Page Life-Cycle

In Smartface Page is a component with life-cycle. The life-cycle of the page is in the following order:

  1. Creation - Constructor

  2. Load - onLoad

  3. Show - onShow

  4. Post-Render

  5. Hide - onHide

  6. Destruction

1. Creation - Constructor

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.

Developer Page Code
UI Editor Generated Page Code
Developer Page Code
/*
You can modify its contents.
*/
const extend = require('js-base/core/extend');
const NewPage001Design = require('ui/ui_newPage001');
const NewPage001 = extend(NewPage001Design)(
// Constructor
function(_super) {
// Initalizes super class for this page scope
_super(this);
// overrides super.onShow method
this.onShow = onShow.bind(this, this.onShow.bind(this));
// overrides super.onLoad method
this.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);
UI Editor Generated Page Code
//------------------------------------------------------------------------------
//
// 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]);
else
this.addChild(this.children[childName]);
}
//constructor
function $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 props
this.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.

What is happening in Page Constructor?

  • Native UI Object references are being created, but they are not ready to process

  • Initial values are being assigned

What to do in Page Constructor

  • Create UI objects

  • Assign page events

  • Create page-wise properties

2 - Page Load

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.

What is happening during Page Load?

  • 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

What to do in Page load

  • 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

3 - Page Show

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.

What is happening during load

  • 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

What to do in Page Show

  • 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

4 - Post Render

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.

postRender Android
postRender Android
/**
* @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 postRenderfunction is called every time when the page is shown. In order to avoid this, a custom property can be created with the page.

One-off postRender Android
One-off postRender Android
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
}

iOS

On iOS there are 3 native different events to do this, in order:

Sample Usages of iOS Page Life-cycle Events
Sample Usages of iOS Page Life-cycle Events
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

Bring them together

Here are two samples for the page are given to get a postRender event.

Fires every time page is Shown - postRender
One-off postRender
Fires every time page is Shown - postRender
const extend = require('js-base/core/extend');
const NewPage001Design = require('ui/ui_newPage001');
const System = require('sf-core/device/system');
const NewPage001 = extend(NewPage001Design)(
// Constructor
function 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);
One-off postRender
const extend = require('js-base/core/extend');
const NewPage001Design = require('ui/ui_newPage001');
const System = require('sf-core/device/system');
const NewPage001 = extend(NewPage001Design)(
// Constructor
function(_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);

5 - Hide

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.

What to do on Page Hide Event, what not to do

  • 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

Sample onHide
Sample onHide
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);

6 - Destruction

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.