Widgets created in async functions do not appear
Problem description
When we append a new view to the contentView within async function, it is not rendered. However, it appears when we move the app to the background and then come back to the foreground. We can reproduce the issue with the following snippet, which new screen (with red background) should be opened after 4 seconds when we click the button on the first yellow screen.
Expected behavior
The rendering of the view should not be affected by any async functions.
Environment
- Tabris.js version: 3.7.2
- Device: iPad 10.2, Samsung A70
- OS: iOS 14.6, Android 11
Code snippet
import {Button, Color, Composite, LayoutData, contentView} from 'tabris';
class Screen extends Composite {
constructor(background) {
super({
layoutData: LayoutData.stretch,
background
});
this.append(
new Button({
left: 16,
right: 16,
centerY: 0,
text: 'Click'
}).onSelect(async () => {
await new Promise(resolve => setTimeout(resolve, 4000)); // without this line it works fine
new Screen(Color.red).appendTo(contentView);
})
);
}
}
new Screen(Color.yellow).appendTo(contentView);
It works fine when rewriting it to use only Promise, and it also works fine when dropping it in to a freshly generated tabris app using the default Jsx/JavaScript template. That's because it's re-written by tsc to use the Promise that tabris provides:
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const tabris_1 = require("tabris");
class Screen extends tabris_1.Composite {
constructor(background) {
super({
layoutData: tabris_1.LayoutData.stretch,
background
});
this.append(new tabris_1.Button({
left: 16,
right: 16,
centerY: 0,
text: 'Click'
}).onSelect(() => __awaiter(this, void 0, void 0, function* () {
yield new Promise(resolve => setTimeout(resolve, 4000)); // without this line it works fine
new Screen(tabris_1.Color.red).appendTo(tabris_1.contentView);
})));
}
}
new Screen(tabris_1.Color.yellow).appendTo(tabris_1.contentView);
So it is really only the native async/await that do not work, and I don't believe we can make it work easily. They are using (I assume) a native Promise implementation provided by the JS VM (V8/corejs) that (naturally) does not know that tabris.flush() needs to be called when a promise is awaited. So the best we can do is document this, unless @mpost and @karolszafranski know a way to modify the built-in Promise/async support.