Page

API Reference: UI.Page

Basic Page

Page class stands for showing different interfaces. Pages can be considered as screens. Every page has its own lifecycle: load, show and hide. Application should have at least one page otherwise user will just see a black screen.

Page has an embedded flex layout, into which you can add views.

Please refer to the related guides for best practices of page usage and page navigation.

See also UI.Pages to see how to display a page on the screen.

TypeScript code blocks include examples of how to implement, override and components within the theme. You can create page with the UI Editor to make your page compatible with theming and then you can implement themable components programmatically. Once the page is created with the UI Editor, it generates a class under scripts/generated/pages. You can then extend that class with the following TypeScript classes.

TypeScript
JavaScript
TypeScript
import PageSampleDesign from 'generated/pages/pageSample';
//You should create new Page from UI-Editor and extend with it.
export default class Sample extends PageSampleDesign {
constructor() {
super();
// 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: () => void) {
superOnShow();
}
/**
* @event onLoad
* This event is called once when page is created.
* @param {function} superOnLoad super onLoad function
*/
function onLoad(superOnLoad: () => void) {
superOnLoad();
}
JavaScript
const Page = require("sf-core/ui/page");
const extend = require("js-base/core/extend");
var Page1 = extend(Page)(
function(_super) {
_super(this, {
onShow: function(params) {
}
});
}
);
module.exports = Page1;

Consider this information when you create your application flow.

On iOS, onShow() function is called when page is about to show. On Android, it is called when page is shown on screen.

Origin of Page

On Android device screen coordinates start after statusBar, the most top left; on iOS it starts the most top left.

Pop-up Page - Modal

Pop-up page act like full-screen dialog, but also it is a fully fledged page. After using the pop-up page, you can dismiss the pop-up page and will return the page that used before pop-up. To use pop-up page, set modal property of route in your router. You can find an example below.

In this example, styling scheme of this guide is used with a few addition. Also, the theme smartfaceLightTheme was used.

More info can be found on page below:

Before you use the example, make sure to create following pages and theme:

  • pgWelcome as page

  • pgLogin as page

  • smartfaceLightTheme as a theme

Router Code
Welcome Page
Welcome UI
Login Page
Login UI
Service Code
Router Code
scripts/routes/index.ts
import buildExtender from "sf-extension-utils/lib/router/buildExtender";
import {
NativeRouter as Router,
NativeStackRouter as StackRouter,
Route
} from "@smartface/router";
import "sf-extension-utils/lib/router/goBack"; // Implements onBackButtonPressed
import System from 'sf-core/device/system';
/**
* On Android, onShow of the dismissed page will not trigger on its own.
* Therefore, we call it ourselves at routeDidEnter event.
*/
const androidModalDismiss = (router, route) => {
const { view, action } = route.getState();
if (System.OS === "Android" && view && action === "POP") {
view.onShow && view.onShow();
}
};
const router = Router.of({
path: "/",
to: "/root",
isRoot: true,
routes: [
StackRouter.of({
path: "/root",
to: "/root/welcome",
routes: [
Route.of({
path: "/root/welcome",
build: buildExtender({
getPageClass: () => require("pages/pgWelcome").default,
headerBarStyle: { visible: false }
}),
routeDidEnter: androidModalDismiss
}),
StackRouter.of({
path: "/root/user",
to: "/root/user/login",
modal: true, // This is essential
routes: [
Route.of({
path: "/root/user/login",
build: buildExtender({
getPageClass: () => require("pages/pgLogin").default,
headerBarStyle: { visible: false }
})
})
]
})
]
})
]
});
export default router;
Welcome Page
scripts/pages/PgWelcome.ts
import PgProfileDesign from 'generated/pages/pgProfile';
import { getUsername, logout } from 'services/login';
export default class PgProfile extends PgProfileDesign {
router: any;
isLoggedIn = false;
constructor() {
super();
this.onShow = onShow.bind(this, this.onShow.bind(this));
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
}
initLoginButton() {
this.btnLogin.onPress = () => {
if (this.isLoggedIn) {
this.logout();
}
else {
this.router.push('user/login');
}
}
}
updateText() {
if (this.isLoggedIn) {
this.lblLogin.text = `Welcome, ${getUsername()}`;
this.btnLogin.text = `Logout`;
}
else {
this.lblLogin.text = `You must login to see your name`;
this.btnLogin.text = `Login`;
}
}
logout() {
logout();
this.isLoggedIn = false;
this.updateText();
}
}
function onShow(superOnShow: () => void) {
superOnShow();
this.isLoggedIn = getUsername().length;
this.updateText();
}
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.initLoginButton();
}
Welcome UI
.ui/pages/pgWelcome.pgx
{
"components": [
{
"className": ".sf-page",
"id": "e891b86d",
"initialized": false,
"props": {
"children": [
"42b807d1",
"b7d28cff",
"88af-db55-523d-267e",
"e2d2-6802-42cf-d32d"
],
"isRemovable": true,
"name": "pgWelcome",
"orientation": "PORTRAIT",
"parent": null,
"safeAreaEnabled": true
},
"type": "Page",
"userProps": {
"flexProps": {
"justifyContent": "SPACE_BETWEEN",
"alignItems": "STRETCH"
},
"orientation": "PORTRAIT",
"paddingBottom": 20,
"paddingLeft": 16,
"paddingRight": 16,
"safeAreaEnabled": true
},
"version": "6.15.1"
},
{
"className": ".sf-statusBar",
"id": "42b807d1",
"props": {
"children": [],
"isRemovable": false,
"name": "statusBar",
"parent": "e891b86d"
},
"type": "StatusBar",
"userProps": {}
},
{
"className": ".sf-headerBar",
"id": "b7d28cff",
"props": {
"children": [],
"isRemovable": false,
"name": "headerBar",
"parent": "e891b86d",
"title": "Profile"
},
"type": "HeaderBar",
"userProps": {
"title": "Profile"
}
},
{
"className": ".sf-label #pgLogin-lblLogin",
"id": "88af-db55-523d-267e",
"props": {
"children": [],
"name": "lblLogin",
"parent": "e891b86d",
"text": "You must login to see your name",
"usePageVariable": true
},
"type": "Label",
"userProps": {
"testId": "tuHBk9XRa",
"text": "You must login to see your name",
"usePageVariable": true
}
},
{
"className": ".sf-button #pgLogin-btnLogin",
"id": "e2d2-6802-42cf-d32d",
"props": {
"children": [],
"name": "btnLogin",
"parent": "e891b86d",
"text": "Login",
"usePageVariable": true
},
"type": "Button",
"userProps": {
"testId": "YXfMaNYMP",
"text": "Login",
"usePageVariable": true
}
}
]
}
Login Page
scripts/pages/PgLogin.ts
import PgLoginDesign from 'generated/pages/pgLogin';
import { login } from 'services/login';
export default class PgLogin extends PgLoginDesign {
router: any;
constructor() {
super();
this.onShow = onShow.bind(this, this.onShow.bind(this));
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
}
initMaterialTextBoxes() {
this.mtbUsername.options = {
hint: "Username"
}
this.mtbPassword.options = {
hint: "Password"
}
this.mtbPassword.materialTextBox.isPassword = true;
}
initButtons() {
this.btnLogin.onPress = async () => {
try {
const username = this.mtbUsername.materialTextBox.text;
const password = this.mtbPassword.materialTextBox.text;
this.btnLogin.text = `Logging in ...`;
await login(username, password);
this.router.dismiss();
}
catch(e) {
// Empty text is not allowed
this.btnLogin.text = `Login`;
}
}
this.btnCancel.onPress = () => {
this.router.dismiss();
}
}
initMockUserPass() {
this.lblLoginText.onTouchEnded = () => {
this.mtbUsername.materialTextBox.text = `Smartface`;
this.mtbPassword.materialTextBox.text = `welcome`;
}
}
}
function onShow(superOnShow: () => void) {
superOnShow();
}
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.initMaterialTextBoxes();
this.initButtons();
this.initMockUserPass();
}
Login UI
.ui/pages/pgLogin.pgx
{
"components": [
{
"className": ".sf-page #pgLogin .padding-vertical",
"id": "e891b86d",
"initialized": true,
"props": {
"children": [
"42b807d1",
"b7d28cff",
"01ee-3b7d-fae4-a0fe",
"e8d0-e028-be62-a7e2",
"80c4-be9c-a3f5-3e3b",
"0855-e9ce-5840-6caf",
"0cff-66df-b749-b9f8",
"4c56-9a45-4bd6-d78a"
],
"isRemovable": true,
"name": "pgLogin",
"orientation": "PORTRAIT",
"parent": null,
"safeAreaEnabled": true
},
"type": "Page",
"userProps": {
"orientation": "PORTRAIT",
"safeAreaEnabled": true
},
"version": "6.15.1"
},
{
"className": ".sf-statusBar",
"id": "42b807d1",
"props": {
"children": [],
"isRemovable": false,
"name": "statusBar",
"parent": "e891b86d"
},
"type": "StatusBar",
"userProps": {}
},
{
"className": ".sf-headerBar",
"id": "b7d28cff",
"props": {
"children": [],
"isRemovable": false,
"name": "headerBar",
"parent": "e891b86d",
"title": "Login"
},
"type": "HeaderBar",
"userProps": {
"title": "Login"
}
},
{
"className": ".sf-label #pgLogin-lblLoginText",
"id": "01ee-3b7d-fae4-a0fe",
"props": {
"children": [],
"name": "lblLoginText",
"parent": "e891b86d",
"text": "Login",
"usePageVariable": true
},
"type": "Label",
"userProps": {
"testId": "31XoZqypi",
"text": "Login",
"usePageVariable": true
}
},
{
"className": ".flexLayout .materialTextBox-wrapper",
"hiddenComponent": false,
"id": "e8d0-e028-be62-a7e2",
"initialized": false,
"props": {
"children": [],
"name": "mtbUsername",
"parent": "e891b86d",
"usePageVariable": true
},
"source": {
"page": "__library__",
"type": "materialTextBox",
"id": "574a-10da-b765-48e5"
},
"type": "FlexLayout",
"userProps": {
"flex": {
"positionType": 0
},
"flexProps": {
"positionType": "RELATIVE"
},
"left": 0,
"testId": "GBgEp8H6g",
"top": 0,
"usePageVariable": true
}
},
{
"className": ".flexLayout .materialTextBox-wrapper",
"hiddenComponent": false,
"id": "80c4-be9c-a3f5-3e3b",
"initialized": false,
"props": {
"children": [],
"name": "mtbPassword",
"parent": "e891b86d",
"usePageVariable": true
},
"source": {
"page": "__library__",
"type": "materialTextBox",
"id": "574a-10da-b765-48e5"
},
"type": "FlexLayout",
"userProps": {
"flex": {
"positionType": 0
},
"flexProps": {
"positionType": "RELATIVE"
},
"left": 0,
"testId": "2VkXyj8Sk",
"top": 0,
"usePageVariable": true
}
},
{
"className": ".sf-label #pgLogin-lblForgotPassword",
"id": "0855-e9ce-5840-6caf",
"props": {
"children": [],
"name": "lblForgotPassword",
"parent": "e891b86d",
"text": "Forgot Password?",
"usePageVariable": true
},
"type": "Label",
"userProps": {
"testId": "cxb0GrKoG",
"text": "Forgot Password?",
"usePageVariable": true
}
},
{
"className": ".sf-button #pgLogin-btnLogin",
"id": "0cff-66df-b749-b9f8",
"props": {
"children": [],
"name": "btnLogin",
"parent": "e891b86d",
"text": "Login",
"usePageVariable": true
},
"type": "Button",
"userProps": {
"testId": "lU2lMxhdz",
"text": "Login",
"usePageVariable": true
}
},
{
"className": ".sf-button #pgLogin-btnLogin",
"id": "4c56-9a45-4bd6-d78a",
"props": {
"children": [],
"name": "btnCancel",
"parent": "e891b86d",
"text": "Cancel",
"usePageVariable": true
},
"type": "Button",
"userProps": {
"testId": "ERnreq7sS",
"text": "Cancel",
"usePageVariable": true
}
}
]
}
Service Code
scripts/services/login.ts
let userName = '';
export function login(username: string, password: string): Promise<string> {
return new Promise((resolve, reject) => {
const token = 'Your token goes here'
if (!username.length) {
reject("Empty username");
}
setTimeout(() => {
userName = username;
resolve(token);
}, 500);
});
}
export function logout() {
userName = '';
}
export function getUsername() {
return userName;
}
Pop-up Android
Pop-up iOS

Do not forget to change your route path defined on app.ts file.

Passing Data between Pages

Like in the example shown at Reveal Pages section, you can pass any data to another page while navigating to another page. However, There is no OOTB (Out of the Box) way to pass a variable while going back.

Check our Router Module to get more information about passing data between pages.

Best Practice of Passing data while going back or dismissing

When passing data in-between modal pages, you should use a mutual medium. In this example, a module scoped variable was used to get username which was received under login page. Normally, a state management like redux should be used. More info can be found at this guide.

Reveal Pages

API Reference: TransitionViews

Transitions are the animated changes between two pages, states or views to provide visual continuity to the user interface. It works like PopupPage. Use transitionId to mark related Views. Each View must be linked with unique id.

TransitionId Linking

TransitionId of View must be set and link before onShow methods and creating instance of second page.

Before running the sample code below, make sure you create the following pages on UI Editor:

  • pgUserList

  • pgUser

While creating the pages, UI Editor was used. Please make sure to add the pages and their UI style properly.

User List UI
User List Page
User UI
User Code
Component UI
Service Code
Router Code
User List UI
.ui/pgUserList.pgx
{
"components": [
{
"className": ".sf-page",
"id": "0216-a586-68ea-ef16",
"initialized": true,
"props": {
"children": [
"e251-dc93-1cce-a061",
"e5bc-0b15-1b36-b8d6",
"c8dc-c2bc-eac7-33e3"
],
"name": "pgUserList",
"orientation": "PORTRAIT",
"parent": null
},
"type": "Page",
"userProps": {}
},
{
"className": ".sf-statusBar",
"id": "e251-dc93-1cce-a061",
"props": {
"children": [],
"isRemovable": false,
"name": "statusBar",
"parent": "0216-a586-68ea-ef16"
},
"type": "StatusBar",
"userProps": {}
},
{
"className": ".sf-headerBar",
"id": "e5bc-0b15-1b36-b8d6",
"props": {
"children": [],
"isRemovable": false,
"name": "headerBar",
"parent": "0216-a586-68ea-ef16",
"title": "User List"
},
"type": "HeaderBar",
"userProps": {
"title": "User List"
}
},
{
"className": ".sf-gridView",
"id": "c8dc-c2bc-eac7-33e3",
"props": {
"children": [
"a41a-fe3a-cfe8-7bb5"
],
"name": "gvUsers",
"parent": "0216-a586-68ea-ef16",
"usePageVariable": true
},
"type": "GridView",
"userProps": {
"flexProps": {
"flexGrow": 1
},
"layoutManager": {
"spanCount": 2
},
"testId": "pRB1URCFU",
"usePageVariable": true
}
},
{
"className": ".sf-gridViewItem .sf-gridViewItem-simple",
"hiddenComponent": false,
"id": "a41a-fe3a-cfe8-7bb5",
"initialized": true,
"props": {
"children": [],
"name": "gviUser",
"parent": "c8dc-c2bc-eac7-33e3"
},
"source": {
"page": "__library__",
"type": "gviUser",
"id": "aea8-55c1-833e-4baa"
},
"type": "GridViewItem",
"userProps": {
"flex": {
"positionType": 0
},
"flexProps": {
"positionType": "RELATIVE"
},
"height": 227,
"left": 0,
"testId": "6PcXnnHpZ",
"top": 0
}
}
]
}
User List Page
scripts/pages/pgUserList.ts
import PgUserListDesign from 'generated/pages/pgUserList';
import { getUserList, User } from 'services/users';
import GviUser from 'components/GviUser';
export default class PgUserList extends PgUserListDesign {
private data: User[] = [];
router: any;
constructor() {
super();
this.onShow = onShow.bind(this, this.onShow.bind(this));
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
}
async getUsers() {
try {
this.data = await getUserList();
this.refreshGridView();
}
catch (e) {
console.error(e);
}
}
initGridView() {
this.gvUsers.onItemBind = (gridViewItem: GviUser, index: number) => {
gridViewItem.lblTitle.text = `${this.data[index].first_name} ${this.data[index].last_name}`;
gridViewItem.lblSubtitle.text = ``;
gridViewItem.imgUser.loadFromUrl({
url: this.data[index].avatar,
useHTTPCacheControl: true
});
}
this.gvUsers.onPullRefresh = async () => {
try {
await this.getUsers();
}
finally {
this.gvUsers.stopRefresh();
}
}
this.gvUsers.onItemSelected = (gridViewItem: GviUser, index: number) => {
//@ts-ignore
gridViewItem.imgUser.transitionId = String(this.data[index].id);
//@ts-ignore
this.transitionViews = [gridViewItem.imgUser];
this.router.push('user/detail', {
transitionImage: gridViewItem.imgUser.image,
imageURL: this.data[index].avatar,
transitionId: String(this.data[index].id),
title: gridViewItem.lblTitle.text,
subTitle: this.data[index].email
})
}
}
refreshGridView() {
this.gvUsers.itemCount = this.data.length;
this.gvUsers.refreshData();
}
}
function onShow(superOnShow: () => void) {
superOnShow();
}
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.initGridView();
this.getUsers();
}
User UI
.ui/pgUser.pgx
{
"components": [
{
"className": ".sf-page",
"id": "f8cf-6172-1673-f91a",
"initialized": true,
"props": {
"children": [
"c67c-e972-e3a7-1b74",
"748e-84f9-8e99-af0c",
"f30b-c1ed-884e-4a1e",
"c651-f5e8-df46-a642",
"0d22-df6d-e330-2c57",
"9265-6282-5efb-696b"
],
"name": "pgUser",
"orientation": "PORTRAIT",
"parent": null,
"safeAreaEnabled": true
},
"type": "Page",
"userProps": {
"safeAreaEnabled": true
}
},
{
"className": ".sf-statusBar",
"id": "c67c-e972-e3a7-1b74",
"props": {
"children": [],
"isRemovable": false,
"name": "statusBar",
"parent": "f8cf-6172-1673-f91a"
},
"type": "StatusBar",
"userProps": {}
},
{
"className": ".sf-headerBar",
"id": "748e-84f9-8e99-af0c",
"props": {
"children": [],
"isRemovable": false,
"name": "headerBar",
"parent": "f8cf-6172-1673-f91a",
"title": "pgUser"
},
"type": "HeaderBar",
"userProps": {
"title": "pgUser",
"visible": false
}
},
{
"className": ".sf-imageView",
"id": "f30b-c1ed-884e-4a1e",
"props": {
"children": [],
"name": "imgProfile",
"parent": "f8cf-6172-1673-f91a",
"usePageVariable": true
},
"type": "ImageView",
"userProps": {
"imageFillType": "ASPECTFIT",
"testId": "Hy6YUKnTK",
"usePageVariable": true
}
},
{
"className": ".sf-label",
"id": "c651-f5e8-df46-a642",
"props": {
"children": [],
"name": "lblTitle",
"parent": "f8cf-6172-1673-f91a",
"text": "label1",
"usePageVariable": true
},
"type": "Label",
"userProps": {
"testId": "Coy_7oKpF",
"text": "label1",
"textAlignment": "MIDCENTER",
"textColor": "rgba( 0, 0, 0, 1 )",
"usePageVariable": true
}
},
{
"className": ".sf-label",
"id": "0d22-df6d-e330-2c57",
"props": {
"children": [],
"name": "lblSubtitle",
"parent": "f8cf-6172-1673-f91a",
"text": "label2",
"usePageVariable": true
},
"type": "Label",
"userProps": {
"testId": "QbQyD2HTK",
"text": "label2",
"textAlignment": "MIDCENTER",
"textColor": "rgba( 0, 0, 0, 1 )",
"usePageVariable": true
}
},
{
"className": ".sf-flexLayout",
"id": "9265-6282-5efb-696b",
"props": {
"children": [
"3701-d69f-bbee-1d5f"
],
"name": "flWrapper",
"parent": "f8cf-6172-1673-f91a"
},
"type": "FlexLayout",
"userProps": {
"flexProps": {
"flexGrow": 1,
"justifyContent": "FLEX_END"
},
"height": null,
"testId": "tKXqeskD0"
}
},
{
"className": ".sf-button",
"id": "3701-d69f-bbee-1d5f",
"props": {
"children": [],
"name": "btnCancel",
"parent": "9265-6282-5efb-696b",
"text": "Cancel",
"usePageVariable": true
},
"type": "Button",
"userProps": {
"testId": "r0pETCd27",
"text": "Cancel",
"usePageVariable": true
}
}
]
}
User Code
scripts/pages/pgUser.ts
import PgUserDesign from 'generated/pages/pgUser';
import Image from 'sf-core/ui/image';
export default class PgUser extends PgUserDesign {
router: any;
routeData: {
transitionImage: Image;
imageURL: string;
transitionId: string;
title: string;
subTitle: string;
};
constructor() {
super();
this.onShow = onShow.bind(this, this.onShow.bind(this));
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
}
initImage() {
//@ts-ignore
this.transitionViews = [this.imgProfile];
//@ts-ignore
this.imgProfile.transitionId = this.routeData.transitionId;
this.imgProfile.image = this.routeData.transitionImage;
}
initTexts() {
this.lblTitle.text = this.routeData.title;
this.lblSubtitle.text = this.routeData.subTitle;
}
initButton() {
this.btnCancel.onPress = () => {
this.router.dismiss();
}
}
}
function onShow(superOnShow: () => void) {
superOnShow();
}
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.initImage();
this.initButton();
this.initTexts();
}
Component UI
.ui/library/gviUser.cpx
{
"components": [
{
"className": ".sf-gridViewItem .sf-gridViewItem-simple",
"hiddenComponent": false,
"id": "aea8-55c1-833e-4baa",
"initialized": true,
"props": {
"children": [
"58e6-e69e-a1c1-d4b9",
"67de-5d59-e18c-7727",
"e92d-12a6-d5ab-b6da"
],
"name": "gviUser",
"parent": "57f4-201f-4bfc-5fc6"
},
"source": {
"page": "__library__"
},
"type": "GridViewItem",
"userProps": {
"flexProps": {},
"height": 227,
"left": 102.77777777777779,
"top": 88.88888888888889
}
},
{
"className": ".sf-imageView .sf-gridViewItem-simple-preview",
"id": "58e6-e69e-a1c1-d4b9",
"props": {
"autoHeight": 343.4782608695652,
"autoWidth": 343.4782608695652,
"children": [],
"name": "imgUser",
"parent": "aea8-55c1-833e-4baa",
"usePageVariable": true
},
"source": {
"page": "__library__"
},
"type": "ImageView",
"userProps": {
"autoHeight": 343.4782608695652,
"autoWidth": 343.4782608695652,
"usePageVariable": true,
"width": 165
}
},
{
"className": ".sf-label .sf-gridViewItem-simple-title",
"id": "67de-5d59-e18c-7727",
"props": {
"autoHeight": 20.652173913043477,
"children": [],
"name": "lblTitle",
"parent": "aea8-55c1-833e-4baa",
"text": "Grid Title Here",
"usePageVariable": true
},
"source": {
"page": "__library__"
},
"type": "Label",
"userProps": {
"autoHeight": 20.652173913043477,
"text": "Grid Title Here",
"usePageVariable": true
}
},
{
"className": ".sf-label .sf-gridViewItem-simple-subtitle",
"id": "e92d-12a6-d5ab-b6da",
"props": {
"autoHeight": 18.478260869565215,
"children": [],
"name": "lblSubtitle",
"parent": "aea8-55c1-833e-4baa",
"text": "Grid subtitle here",
"usePageVariable": true
},
"source": {
"page": "__library__"
},
"type": "Label",
"userProps": {
"autoHeight": 18.478260869565215,
"text": "Grid subtitle here",
"usePageVariable": true
}
}
]
}
Service Code
scripts/services/users.ts
const smartfaceItem = {
id: 0,
first_name: "Smartface",
last_name: "Inc.",
avatar: "https://upload.wikimedia.org/wikipedia/en/7/7d/Smartface_Logo.png"
}
export type User = {
id: number;
email: string;
first_name: string;
last_name: string;
avatar: string; // Url
}
type UserList = {
data: User[]
}
export async function getUserList(): Promise<UserList['data']> {
return new Promise((resolve, reject) => {
setTimeout(() => {
let response: User[] = Array(16).fill(smartfaceItem);
response = response.map((item, index) => {
const newItem = Object.assign({}, item);
newItem.id = index;
return newItem;
});
resolve(response);
}, 500);
})
}
Router Code
scripts/routes/index.ts
import buildExtender from "sf-extension-utils/lib/router/buildExtender";
import {
NativeRouter as Router,
NativeStackRouter as StackRouter,
Route
} from "@smartface/router";
import "sf-extension-utils/lib/router/goBack"; // Implements onBackButtonPressed
import "sf-extension-utils/lib/router/back-close";
import System from 'sf-core/device/system';
/**
* On Android, onShow of the dismissed page will not trigger on its own.
* Therefore, we call it ourselves at routeDidEnter event.
*/
const androidModalDismiss = (router, route) => {
const { view, action } = route.getState();
if (System.OS === "Android" && view && action === "POP") {
view.onShow && view.onShow();
}
};
const router = Router.of({
path: "/",
to: "/root",
isRoot: true,
routes: [
StackRouter.of({
path: "/root",
to: "/root/userlist",
routes: [
Route.of({
path: "/root/userlist",
build: buildExtender({
getPageClass: () => require("pages/pgUserList").default
}),
routeDidEnter: androidModalDismiss
}),
StackRouter.of({
path: "/root/user",
to: "/root/user/detail",
modal: true, // This is essential
routes: [
Route.of({
path: "/root/user/detail",
build: buildExtender({
getPageClass: () => require("pages/pgUser").default,
headerBarStyle: { visible: false }
})
})
]
})
]
})
]
});
export default router;

Do not forget to change your main route push on app.ts

The guides which are used:

Also, take a look into Router Documentation

Reveal iOS
Reveal Android