In Smartface Page is a component with life-cycle. The life-cycle of the page is in the following order:
Pages in Smartface defined as a JavaScript Class. New instances form those classes are created on demand. Router, Navigator or SwipeView calls the constructor of the class to create a new instance of the page. This phase of the page life-cycle is achieved once per page instance. Singleton pages are created only once per definition.
/*You can modify its contents.*/const extend = require('js-base/core/extend');const NewPage001Design = require('ui/ui_newPage001');​const NewPage001 = extend(NewPage001Design)(// Constructorfunction(_super) {// Initalizes super class for this page scope_super(this);// overrides super.onShow methodthis.onShow = onShow.bind(this, this.onShow.bind(this));// overrides super.onLoad methodthis.onLoad = onLoad.bind(this, this.onLoad.bind(this));​});​/*** @event onShow* This event is called when a page appears on the screen (everytime).* @param {function} superOnShow super onShow function* @param {Object} parameters passed from Router.go function*/function onShow(superOnShow) {superOnShow();}​/*** @event onLoad* This event is called once when page is created.* @param {function} superOnLoad super onLoad function*/function onLoad(superOnLoad) {superOnLoad();}​module && (module.exports = NewPage001);
//------------------------------------------------------------------------------//// This code was auto generated. (6.6.4)//// Manual changes to this file may cause unexpected behavior in your application.// Manual changes to this file will be overwritten if the code is regenerated.////------------------------------------------------------------------------------​const extend = require('js-base/core/extend');const PageBase = require('sf-core/ui/page');const Page = extend(PageBase);const pageContextPatch = require('@smartface/contx/lib/smartface/pageContextPatch');​function addChild(childName, ChildClass, pageInstance) {this.children = this.children || {};this.children[childName] = new ChildClass(pageInstance);if (this.layout)this.layout.addChild(this.children[childName]);elsethis.addChild(this.children[childName]);}//constructorfunction $NewPage001(_super, props) {// initalizes super class for this page scope_super(this, Object.assign({}, {onShow: onShow.bind(this)}, props || {}));this.children = {};this.children["statusBar"] = this.statusBar;this.children["headerBar"] = this.headerBar;​pageContextPatch(this, "newPage001");}$NewPage001.$$styleContext = {classNames: ".page",userProps: {},statusBar: {classNames: ".statusBar",userProps: {}},headerBar: {classNames: ".headerBar",userProps: {}}};const $NewPage001_ = Page($NewPage001);​/*** @event onShow* This event is called when a page appears on the screen (everytime).* @param {Object} parameters passed from Router.go function*/function onShow() {//HeaderBar propsthis.headerBar.title = "newPage001";​}​module && (module.exports = $NewPage001_);
The code above is created when a new page is created via UI editor (6.6.4). Constructors of the page are mentioned with the inline comments. Those code blocks are the ones that are going to be called first.
Native UI Object references are being created, but they are not ready to process
Initial values are being assigned
Create UI objects
Assign page events
Create page-wise properties
Page load is called later by the native system sometime later than the constructor. The load event is not called more than once during the whole life-cycle of the page. UI operations in this step have less impact on performance rather than the later steps because UI rendering has not started yet. This phase of the page life-cycle is achieved once per page instance. Singleton pages are created only once per definition.
UI Object references are processed. They are ready to use
Properties are ready to use
For the pages that are created by UI editor, initial theming has been applied
Read & change property values
Assign events to child objects
Execute one-off code, such as static data loading
For HeaderBar, create items, set their events, assign to page.headerBar
Setup style of headerbar
Make static bindings, such as language texts, RTL-LTR
Set ScrollView layout height or width (based on direction), if the content is fixed
Show event of the page is called every time, just before the page is shown. This event is fired after the page is loaded and can be fired more than one time. This phase of the page life-cycle may be achieved more than once per page instance. Having a singleton page has no effect on this.
Some of the UI object references are created here (HeaderBar & StatusBar)
Smartface is setting up the navigation history
A partial rendering of the page starts
Set (every time) visibility of StatusBar, is at least one page contradicts to others
Set (every time) visibility of HeaderBar, is at least one page contradicts to others
Setup dynamic data binding
If possible and if required, after data binding set ScrollView layout height or width (based on direction) for dynamic content
In this phase layout calculations and rendering is completed. Height and width of the components can be read. This becomes handy while the size of the component is not known fully, such as auto-sized components (Image & Text)
In most of the cases, the developer does not need to use this phase of the page. If it is still required, for iOS & Android it has to be handled differently.
This phase of the page life-cycle may be achieved more than once per page instance. Having a singleton page has no effect on this.
## Android
In Android, UI rendering is mostly completed in phase 3 - Show
. In some cases, there can be some components which are not still completed. It is advised to check the values within the onShow
event if they are not fully completed at that step, proceed the with the suggested approach below.
The developer needs to write setTimeout
within the onShow of the page.
/*** @event onShow* This event is called when a page appears on the screen (everytime).* @param {function} superOnShow super onShow function* @param {Object} parameters passed from Router.go function*/function onShow(superOnShow) {superOnShow();setTimeout(postRender.bind(this), 50);}​​function postRender() {const page = this;// do your postRender operation}
In the example above, after 50
ms, the postRender operation is triggered. This can have some effects:
The 50 ms might not be sufficient for some devices, if that is the case, do not hesitate to increase the delay
If the 50 ms is quite long then this can lead to some movement on the UI.
This postRender
function is called every time when the page is shown. In order to avoid this, a custom property can be created with the page.
const NewPage001 = extend(NewPage001Design)(function(_super) {_super(this);this.onShow = onShow.bind(this, this.onShow.bind(this));this.onLoad = onLoad.bind(this, this.onLoad.bind(this));this.pageIsShown = false;});​/*** @event onShow* This event is called when a page appears on the screen (everytime).* @param {function} superOnShow super onShow function* @param {Object} parameters passed from Router.go function*/function onShow(superOnShow) {superOnShow();!this.pageIsShown && setTimeout(postRender.bind(this), 50);this.pageIsShown = true;}​​function postRender() {const page = this;// do your postRender operation}
​
On iOS there are 3 native different events to do this, in order:
​viewWillLayoutSubviews - page.nativeObject.onViewLayoutSubviews
​viewDidLayoutSubviews - page.nativeObject.onViewDidLayoutSubviews
​viewDidAppear - page.nativeObject.onViewDidAppear iOS native life-cylce of those events are explained in iOS UIViewController lifecycle​
const Page = require("sf-core/ui/page");const extend = require("js-base/core/extend");const Page1 = extend(Page)(function(_super) {_super(this, {onShow: function(params) {},onLoad: function(params) {}});this.nativeObject.onViewLayoutSubviews = onViewLayoutSubviews.bind(this,this.nativeObject.onViewLayoutSubviews &&this.nativeObject.onViewLayoutSubviews.bind(this));​this.nativeObject.onViewDidLayoutSubviews =onViewDidLayoutSubviews.bind(this,this.nativeObject.onViewDidLayoutSubviews &&this.nativeObject.onViewDidLayoutSubviews.bind(this));​this.nativeObject.onViewDidAppear = onViewDidAppear.bind(this,this.nativeObject.onViewDidAppear &&this.nativeObject.onViewDidAppear.bind(this));});module.exports = Page1;​function onViewLayoutSubviews(superOnViewLayoutSubviews) {superOnViewLayoutSubviews && superOnViewLayoutSubviews();console.log("Over onViewLayoutSubviews");}​function onViewDidLayoutSubviews(superOnViewDidLayoutSubviews) {superOnViewDidLayoutSubviews && superOnViewDidLayoutSubviews();console.log("Over onViewDidLayoutSubviews");}​function onViewDidAppear(superOnViewDidAppear) {superOnViewDidAppear && superOnViewDidAppear();console.log("Over onViewDidAppear");}
The onViewDidLayoutSubviews and onViewDidAppear events are most suitable events to use.
Both of them are going to be fired everytime page is shown
onViewDidLayoutSubviews will be fired when orientation is changed, or somehow resized; onViewDidAppear will not be fired in those cases
Here are two samples for the page are given to get a postRender event.
const extend = require('js-base/core/extend');const NewPage001Design = require('ui/ui_newPage001');const System = require('sf-core/device/system');​const NewPage001 = extend(NewPage001Design)(// Constructorfunction NewPage001(_super) {// Initalizes super class for this page scope_super(this);this.onShow = onShow.bind(this, this.onShow.bind(this));this.onLoad = onLoad.bind(this, this.onLoad.bind(this));​if (System.OS === "iOS")this.nativeObject.onViewDidLayoutSubviews =onViewDidLayoutSubviews.bind(this,this.nativeObject.onViewDidLayoutSubviews &&this.nativeObject.onViewDidLayoutSubviews.bind(this));​});​/*** @event onShow* This event is called when a page appears on the screen (everytime).* @param {function} superOnShow super onShow function* @param {Object} parameters passed from Router.go function*/function onShow(superOnShow) {superOnShow();if (System.OS === "Android")setTimeout(postRender.bind(this), 50);​}​/*** @event onLoad* This event is called once when page is created.* @param {function} superOnLoad super onLoad function*/function onLoad(superOnLoad) {superOnLoad();}​function onViewDidLayoutSubviews(superOnViewDidAppear) {superOnViewDidAppear && superOnViewDidAppear();postRender.call(this);}​function postRender() {const page = this;// do your postRender operation}​module && (module.exports = NewPage001);​
const extend = require('js-base/core/extend');const NewPage001Design = require('ui/ui_newPage001');const System = require('sf-core/device/system');​const NewPage001 = extend(NewPage001Design)(// Constructorfunction(_super) {// Initalizes super class for this page scope_super(this);this.onShow = onShow.bind(this, this.onShow.bind(this));this.onLoad = onLoad.bind(this, this.onLoad.bind(this));this.pageIsShown = false;​if (System.os === "iOS")this.nativeObject.onViewDidLayoutSubviews =onViewDidLayoutSubviews.bind(this,this.nativeObject.onViewDidLayoutSubviews &&this.nativeObject.onViewDidLayoutSubviews.bind(this));​});​/*** @event onShow* This event is called when a page appears on the screen (everytime).* @param {function} superOnShow super onShow function* @param {Object} parameters passed from Router.go function*/function onShow(superOnShow) {superOnShow();if (System.os === "Android") {!this.pageIsShown && setTimeout(postRender.bind(this), 50);this.pageIsShown = true;}​}​/*** @event onLoad* This event is called once when page is created.* @param {function} superOnLoad super onLoad function*/function onLoad(superOnLoad) {superOnLoad();}​function onViewDidLayoutSubviews(superOnViewDidAppear) {superOnViewDidAppear && superOnViewDidAppear();!this.pageIsShown && postRender.call(this);this.pageIsShown = true;}​function postRender() {const page = this;// do your postRender operation}​module && (module.exports = NewPage001);
​
Hide event is called when another page is shown instead of this one. This phase of the page may not be achieved at all if the application terminates before. A page which has not shown, will not be hidden.
Do not modify HeaderBar and StatusBar
No need to store the state of the page, but the data state might be stored if needed
Do not perform animations or UI operations on the page, this will be a waste of performance
Might need to keep track of user time on the page for analytics purposes
const extend = require('js-base/core/extend');const NewPage001Design = require('ui/ui_newPage001');​const NewPage001 = extend(NewPage001Design)(function NewPage001(_super) {_super(this);this.onShow = onShow.bind(this, this.onShow.bind(this));this.onLoad = onLoad.bind(this, this.onLoad.bind(this));this.onHide = onHide.bind(this, this.onHide && this.onHide.bind(this));});​function onShow(superOnShow) {superOnShow();}​function onLoad(superOnLoad) {superOnLoad();}​function onHide(superOnHide) {superOnHide && superOnHide();}​module && (module.exports = NewPage001);
After a page is created (passed all the steps before), is not currently visible, there are no references to the page instance object, then this JavaScript object is marked for safe to delete. The JavaScript garbage collector is cleaning up that object. As with the garbage collector is clearing the JavaScript counterpart of the object, native side of the object will be destroyed too. There is no event to capture the destruction of the page. References to singleton page instances are always kept by Router or Navigator. Therefore they will never be marked to be deleted. Mozilla documentation about Memory Management is giving best practices for keeping references to JavaScript object. There can be slight differences in the implementation of JavaScript Engines; iOS is using Apple JavaScriptCore, Android is using Google V8.
​