Release Notes, 6.17.0
🚧 Minimum required:
@smartface/native v4.4.0 🚀
Android -> v6.17.0 🚀
iOS -> v6.17.0 🚀
CLI -> v6.17.0 🚀
Project Structure Changes
In this release, we focused on architectural changes and aimed to improve the structure of Smartface Projects. Therefore, we have made a few changes and added a few repositories to our arsenal!
New Modules
- Smartface now uses it's own Typescript Watcher:
- We have developed mixins module to gather our common mixins for better coverage and to add more composition through the project. For usage, keep reading.
- In addition to context, we have added styling-context module for style based concepts.
- For core patterns and code, we have added core module.
These repositories should be added in the project:
# Root folder
yarn add @smartface/tsc-watch -W
# Script Folder
yarn add @smartface/mixins
yarn add @smartface/styling-context
yarn add @smartface/core
Updated Modules
In order to fully migrate to the new version, you should update these packages:
- MaterialTextBox -> Minimum version: 7.0.0
- KeyboardLayout -> Minimum version: 5.0.0
- Contx -> Minimum Version: 4.2.2
- Builder -> Minimum Version: 0.8.0
- Dispatcher -> Minimum Version: 0.7.0
- Marketplace Service -> Minimum Version: 1.2.0
- Marketplace Service -> Minimum Version: 1.2.0
- Image Processor -> Minimum Version: 1.2.1
- Styling Context -> Minimum version: 1.1.9
- Mixins -> Minimum version: 1.0.6
For further information about versions, check the playground project of Smartface for updated versions and match yours with the new ones:
To access playground itself, check the repository:
https://github.com/smartface/sf-playground
Code Changes
Theme Folder
Within the addition of styling context, you first need to change scripts/theme.ts
file to the code below:
import Data from "@smartface/native/global/data";
import Application from "@smartface/native/application";
import { config } from "settings.json";
import { ThemeService } from "@smartface/styling-context/lib/ThemeService";
const themeConfig = config.theme;
const currentTheme = Data.getStringVariable("currentTheme") || themeConfig.currentTheme;
const themeSources = themeConfig.themes.map((name) => ({
name,
rawStyles: require(`./generated/themes/${name}`),
isDefault: currentTheme === name,
}));
export const themeService = new ThemeService(themeSources);
Application["theme"] = ThemeService.instance;
If you have custom code in this file, do not forget to adapt it to the new structure.
Context Functions
With the type addition on dispatch
method, you can type safely use dispatch
method to fit your needs and update your style. For more information, check this documentation:
Keep in mind that this usage is discouraged and might break your Application on the new structure:
import { pushClassNames, removeClassNames } from "@smartface/contx/lib/styling";
//...
this.layout.dispatch(pushClassNames('.sf-flexLayout'));
this.layout.dispatch(removeClassNames('.sf-flexLayout'));
instead, you should be doing dispatch only:
this.layout.dispatch({
type: "pushClassNames",
classNames: '.sf-flexLayout'
});
this.layout.dispatch({
type: "removeClassName",
className: '.sf-flexLayout'
});
Context Error on Generated Code
If you get an error from generated pages after updating similar to this:
scripts/generated/pages/page1/index.ts:129:21 - error TS2345: Argument of type '{ type: string; name: stri
ng; component: string; classNames: string | string[]; defaultClassNames: string | string[]; userStyle: { [key: strin
g]: any; }; }' is not assignable to parameter of type 'Actions'.
129 this.dispatch(actionAddChild(`item${++this.itemIndex}`, item));
That means your builder version is old. Simply update your packages to required minimum versions.
Don't forget to restart your Builder after reinstalling the package.
Style Changes
With the addition of styling-context
, overall style usage have been changed either.
Getting Style From Code
The old usage consisted of using getCombinedStyle
utility. We have removed the need to use utility for this case:
- Old Usage
- New Usage
import { getCombinedStyle } from '@smartface/extension-utils/lib/getCombinedStyle';
const style = getCombinedStyle('.sf-flexLayout');
import { themeService } from 'theme';
const styleNative = themeService.getNativeStyle('.sf-flexLayout'); // For getting native interpolation of styles. It is recommended to use this.
const style = themeService.getStyle('.sf-flexLayout'); // For getting raw styles (like key-value on the theme)
For more information, check the following documentation:
Getting Style Properties From CodeUsage of componentContextPatch
In the old structure, in order to add a component to the context without connecting it to the page, we would be using componentContextPatch
method.
We have deprecated that usage and moved on to a better usage!
- Old Usage
- New Usage
import componentContextPatch from "@smartface/contx/lib/smartface/componentContextPatch";
const flexLayout = new FlexLayout();
componentContextPatch(flexLayout, "flexLayoutGeneric");
import { themeService } from 'theme';
const flexLayout = new FlexLayout();
themeService.addGlobalComponent(flexLayout, "flexLayoutGeneric");
Page Life Cycle Events
With the architectural upgrade (since @smartface/builder@0.7.0
) on builder, it can be converted to a more readable version as shown on the New Page Class Tab.
Now, the callbacks are implemented in more object oriented fashion, allowing to inherit them easier.
- Old Page Class
- New Page Class
import Page1Design from 'generated/pages/page1';
export default class Page1 extends Page1Design {
constructor() {
super();
this.onShow = onShow.bind(this, this.onShow.bind(this));
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
}
}
function onShow(superOnShow: () => void) {
superOnShow();
}
function onLoad(superOnLoad: () => void) {
superOnLoad();
}
import Page1Design from "generated/pages/page1";
import { Route, BaseRouter as Router } from "@smartface/router";
export default class Page1 extends Page1Design {
constructor(private router?: Router, private route?: Route) {
super({});
}
onShow() {
super.onShow();
}
onLoad() {
super.onLoad();
}
}
Router Changes
The old architecture was dependant of the Smartface Utility heavily. Within the new changes, this dependency have been removed on core implementations.
For example, at the old scripts/routes/index.ts
file, usage of back and dismiss button implementations relied on utility.
See the utility dependencies in the old usage? Within the new changes, the back&dimiss button logic have been moved into Page by mixins:
- Old Usage
- New Usage
import buildExtender from "@smartface/extension-utils/lib/router/buildExtender";
import { NativeRouter as Router, NativeStackRouter as StackRouter, Route, BottomTabBarRouter } from "@smartface/router";
import "@smartface/extension-utils/lib/router/goBack"; // Implements onBackButtonPressed
import backClose from '@smartface/extension-utils/lib/router/back-close';
import Image from "@smartface/native/ui/image";
import Page from "@smartface/native/ui/page";
const ROOT_PATH = '/root';
const TAB_PREFIX = 'tab';
backClose.setDefaultBackStyle({
image: Image.createFromFile('images://close_icon.png'),
hideTitle: false
});
backClose.dismissBuilder = () => {
return {
image: Image.createFromFile("images://backarrow.png"),
position: backClose.DismissPosition.LEFT
};
};
const router = Router.of({
path: '/',
isRoot: true,
routes: [
StackRouter.of({
path: '/pages',
routes: [
Route.of({
path: '/pages/page1',
build: buildExtender({
getPageClass: () => require('pages/page1').default,
headerBarStyle: { visible: true },
}),
}),
Route.of({
path: '/pages/page2',
build: buildExtender({
getPageClass: () => require('pages/page2').default,
headerBarStyle: { visible: true },
}),
}),
],
}),
],
});
export default router;
import * as Pages from 'pages';
import { NativeRouter as Router, NativeStackRouter as StackRouter, Route } from '@smartface/router';
import Application from '@smartface/native/application';
/**
* Notice that this method was not present in the old version. It was being overridden in the goBack utility!
*/
Application.on(Application.Events.BackButtonPressed, () => {
Router.getActiveRouter()?.goBack();
});
const router = Router.of({
path: '/',
isRoot: true,
routes: [
StackRouter.of({
path: '/pages',
routes: [
Route.of({
path: '/pages/page1',
build(router, route) {
return new Pages.Page1(router, route);
}
}),
Route.of({
path: '/pages/page2',
build(router, route) {
return new Pages.Page2(router, route);
}
})
]
})
]
});
export default router;
Usage of Mixins and Changing Back&Dismiss Buttons
Since the utility usage have been deprecated, in order to make the page classes more concise, you can use the mixins provided by Smartface!
Let's look at the page class implementation with and without mixin:
- Without Mixin
- With Mixin
import Page1Design from "generated/pages/page1";
import { Route, BaseRouter as Router } from "@smartface/router";
export default class Page1 extends Page1Design {
constructor(private router?: Router, private route?: Route) {
super({});
}
onShow() {
super.onShow();
}
onLoad() {
super.onLoad();
}
}
import Page1Design from "generated/pages/page1";
import { Route, BaseRouter as Router } from "@smartface/router";
import { withDismissAndBackButton } from '@smartface/mixins';
export default class Page1 extends withDismissAndBackButton(Page1Design) { // Notice Page1Design being passes as parameter and returned as a function
constructor(private router?: Router, private route?: Route) {
super({});
}
onShow() {
super.onShow();
this.initDismissButton(this.router); /** Addes a mutual dismiss button that dismisses the modal. Use if your page is going to be modal */
this.initBackButton(this.router); /** Addes a mutual back button that goes back one page. */
}
onLoad() {
super.onLoad();
}
}
For usage, just hover over the functions and see what are their capabilities!
By default, this.initDismissButton
uses close_icon.png
as image. Remember to add it to your project as image.
You can find the default icon by clicking the link:
Check the following documentation on how to generate image(s):
Image GenerationUsage of routeData
Within the new implementation, routeData
will not be added to the page anymore. In the old usage, we could do this.routeData
but now, we can't do that as is. We should add it ourselves.
import Page1Design from "generated/pages/page1";
import { Route, BaseRouter as Router } from "@smartface/router";
export default class Page1 extends Page1Design {
routeData: Record<string, any> = this.route.getState().routeData; // Or if you are sure of what type it will receive, you can strongly type it.
constructor(private router?: Router, private route?: Route) {
super({});
}
onShow() {
super.onShow();
console.info(this.routeData); // Use it!
}
onLoad() {
super.onLoad();
}
}
Getting the Instance of Page
For abstraction purposes, we have deprecated active
utility and discourage getting the page instance. You should pass the page instance from the pages you want to use.
Android Changes
Since we upgraded to a new v8 version, PackageProfiles.xml must also be updated accordingly. To do this go to your PackageProfiles.xml file under the config/Android folder and remove armeabi="1"
from there. Otherwise, your project will fail on build steps.
<?xml version="1.0" encoding="utf-8" ?>
<packageProfiles>
<profile name="Default">
<android x86="0" arm64-v8a="1">
<!-- ... rest of your xml -->
</android>
</profile>
</packageProfiles>
Our newly updated v8 version also requires changes on NDK version. If you are using Appcircle, the following script needs to be added to your build workflow with a new Custom Script
after the Smartface Eject for Android
step.
set -e
set -x
wget -q --show-progress https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip -O android-ndk-r19c-temp.zip \
&& unzip -q android-ndk-r19c-temp.zip -d $AC_REPOSITORY_DIR/ndk \
&& rm android-ndk-r19c-temp.zip
touch $AC_PROJECT_PATH/local.properties
echo "ndk.dir=$AC_REPOSITORY_DIR/ndk/android-ndk-r19c" >> $AC_PROJECT_PATH/local.properties
For local builds, you can refer to:
Building Apps LocallyIDE Changes
IDE Releases are held in its respective place. You can reach it under:
https://github.com/smartface/smartface-ide-releases/releases
For IDE Documentation&Usage, you can refer to this documentation:
UI Editor BasicsNative and Framework Changes
🆕 What's New
- Feature: App shortcuts added. By app shortcuts, you can define shortcuts to perform specific actions in your app. These shortcuts can be displayed in a supported launcher or assistant, like Google Assistant, and help your users quickly start common or recommended tasks within your app
- Improvement: Material components have updated: The affected components:
BottomTabbar
,MaterialTextbox
andTabbarController
. These components are refactored to adapt the latest Material Design Guidelines. - Feature: Multiple image and video pick support added. By using pickMultipleFromGallery, you can now pick multiple items from the gallery.
- Removal: Removed FloatingMenu component. Removed FloatingMenu component from both platforms.
- Feature: Added launchCropper method to Multimedia. This will help you to use native cropper to crop your images at your will.
- Feature: dirty method have been added to View. Now Views can be marked as dirty and the layouts will be recalculated. The dirty method is useful for text-based views such as Label, MaterialTextBox etc. Check the applyLayout documentation for more details.
- Feature: isVoiceOverEnabled property has been added to Application class. It can be used to check if the voiceover is active.
- Feature: hasCameraFeature property has been added to Multimedia class. It can be used to check if the device has an available camera feature.
- Revert:
shareText
&shareImage
&shareFile
methods are rewamped: This methods are no longer deprecated due to some Android devices not allowing to share without declaring the metadata. - Feature: Added support for using Smartface emulator over USB cable: Smartface emulator can communicate with Smartface IDE without WiFi/LAN on Android using ADB as connection medium. Check details on IDE
- Feature: Gridview now supports rowRange methods just like ListView:
- Feature: isEmulator method have been added to determine if the current running application is running under emulator or not.
- Improvement: Added proper typing to
dispatch
method while using context changes.
Android Changes
- Maintenance: Facebook yoga library version has updated to v1.19.0: Smartface uses it to provide a cross-platform layout engine which implements Flexlayout.
- Migration: Migrated from Picasso to Glide: Glide is an image loading and caching library that used in ImageView functions that loadFromUrl, loadFromFile and fetchFromUrl.
- Feature: displayZoomControls added to WebView: Used to set the visibility of zoom controls.
- Feature: Added Android support for canOpenUrl: Use canOpenUrl method to check whether url can launch.
- Feature: Added maxLines property to MaterialTextbox: Used to set the max lines count for MaterialTextbox.
- Migration: Migrated some dependencies to JitPack: Migrated the BlurView, Transcoder, RangeBarSlider and VideoPlayer (Exoplayer) componentes from JCenter to JitPack package repository.
- Improvement: Improved application call: Remove the Package Manager query checks.
- Maintenance: V8 version upgraded to v9.3: Smartface uses v8 JavaScript engine in Android projects. We have upgraded to one of the newest versions, thus the .apk package build size has also increased.
- Maintenance: Armv7a cpu architecture being fully supported
- Maintenance: Minimum supported Android NDK version increased to r19c
- Improvement: HeaderBar Item Design has been improved: Padding and contentInsetStartWithNavigation properties added to HeaderBar for Android.
- Removal: Multimedia.getAllGalleryItems method is removed.
iOS Changes
- Feature: Added expandsOnOverflow property to MaterialTextbox: Use expandsOnOverflow property in order to make auto grow MaterialTextBox.
🐞 Bug Fix
- Fixed the cursor position when new text is set on SearchView for Android.
- Fixed the LiveMediaPlayer not starting up correctly when resuming on
Application.onMaximize
event - Fixed the BlurView causing unexpected behaviors when initialized.
- Fixed the that next character input does not trigger onTextChanged callback when set the text inside onTextChanged in SearchView.
- Fixed the onFailure callback is not executing when an empty URL is given to
imageView.loadFromUrl.
📑 Documentation
The following docs are updated, you can have a look to see what's new:
- First Project on Smartface IDE
- Using Style and Classes
- Using Git with Smartface IDE to Manage and Sync Projects
- ImageView
- How to get the instance of current page on elsewhere in project?
- SearchView
- Getting Style Properties from Code
Glide
Glide is a fast and efficient image loading library and caching library for Android focused on smooth scrolling. Glide supports fetching, decoding, and displaying video stills, images, and animated GIFs.
Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is also effective for almost any case where you need to fetch, resize, and display a remote image.
ImageView functions that loadFromUrl, loadFromFile and fetchFromUrl are using glide as a library.
Previously, we were using Picasso as an image loading and caching library. But we were facing customization and performance issues. Due to that issues, we migrated from picasso to glide.
For more info about ImageView you can refer to:
ImageViewDirty Method for Layout Recalculations
With this version, we have implemented a new method called dirty() for Views, now Views can be marked as dirty and the layouts will be recalculated. The dirty method is useful for text-based views such as Label, MaterialTextBox etc.
const label = new Label({ text: "Hello World" });
label.dirty();
label.text = "Some long text";
There is one difference between Android and iOS while using the dirty method. in iOS, applyLayout()
method needs to be called whenever the dirty method is used in order to safely recalculate the layouts. An example code below can be found below:
import System from "@smartface/native/device/system";
const label = new Label({ text: "Hello World" });
label.dirty();
label.text = "Some long text";
if (System.OS === System.OSType.iOS) {
this.layout.applyLayout();
}
For more information, check the applyLayout documentation in order to learn more about re-calculating layouts
ApplyLayout