NativeScript
Jump to navigation
Jump to search
THIS IS A DRAFT
This text may not be complete.
NativeScript with Angular 4 ⌘
NativeScript with Angular 4 Training Materials
Introduction to NativeScript with Angular ⌘
- build both a web and a native mobile app from a single project
- Schematics
- ng add
Schematics ⌘
- Work of Angular and NativeScript teams
- nativescript-schematics
ng add ⌘
- Use Angular CLI and NativeScript schematics to generate a brand new project with a code-sharing setup
- https://github.com/nativescript/nativescript-schematics
- @nativescript/schematics package only works with @angular/cli: 6.1.0 or newer
- Exercise is here
Installation and setup of NativeScript ⌘
- Quick setup - no access to Plugins, Resources, Debugging, Unit Tests and Lazy Loading, but easy to start with
- Common setup - full access to all the goodies of local dev envy
- on windows and mac-os is quite straight away (usually via tns command)
- on linux it is more demanding (more manual setup)
- and we can only setup it up for android dev tools
- for ios we might to consider sidekick extension ( https://www.nativescript.org/nativescript-sidekick )
Basic setup with telerik cli ⌘
tns create myAppName --ng cd myAppName tns platform add android tns platform add ios # only on mac system or via cloud
tns create myApp --template tns-template-blank-ng
Basic setup with telerik cli con't ⌘
tns doctor cd $ANDROID_HOME/tools/bin sdkmanager "system-images;android-25;google_apis;x86" ./avdmanager create avd -n test -k "system-images;android-25;google_apis;x86" tns device android --available-devices tns run android --emulator tns run android --device test (adb kill-server)
Overview of NativeScript and the Application Life Cycle ⌘
- Technology stack
- Modules
- Lifecycle
Life cycle ⌘
- Component
- Nativescript
- Android
- iOS
- More: https://docs.nativescript.org/angular/core-concepts/application-lifecycle
Nativescript ⌘
NativeScript life cycle events:
- launch
- suspend
- resume: resumed after it has been suspended
- displayed: UIelements are rendered
- orientationChanged
- exit: app is about to exit
- lowMemory: memory on the target device is low
- uncaughtError: on uncaught application error
Overview of Property System, Data Binding and Events ⌘
- Data Binding - connection (binding) between Data Model (Model) and User Interface (UI)
- One-way - from Model to UI, i.e. text stored in Model and displayed on UI in a text area control
- One-way to source (to model) - updates Model due to some action on UI, i.e. button click (tap)
- Two-way data binding - combines both, i.e. text box field that reads its value from Model, but also changes the Model based on user input
- Examples - Angular_2_Fundamentals#Binding_data_.E2.8C.98
Understanding NativeScript's Multithreading Module ⌘
- NS allows fast and efficient access to all native platform (Android/Objective-C) APIs through JavaScript
- without using (de)serialization or reflection
- tradeoff - all JavaScript executes on the main thread (AKA the UI thread)
- operations that potentially take longer can lag the rendering of the UI (slow)
- Solution - multithreading with worker threads
- scripts executing on a background thread in isolated context
- More: https://docs.nativescript.org/angular/core-concepts/multithreading-model
- Example: https://github.com/NativeScript/worker-loader
NS Multithreading con't ⌘
Creating a User Interface with NativeScript ⌘
NativeScript Ui
- set of free(but not fully open-sourced) components
- is built on top of natively implemented components targeting iOS and Android
- they are available for download on npmjs.com as a separate packages
- More: https://docs.nativescript.org/angular/ui/professional-ui-components/overview
- Examples: https://github.com/NativeScript/nativescript-ui-samples-angular
Designing the Application Logic ⌘
Sketching out a rough idea of our app view
- no need to know what it will look like yet
- think about the user expectations
- various sections or modules we need to construct to meet those expectations
- think about the various states the app needs to manage
- map the view into services and states
Modularize with @NgModule ⌘
We can then think about breaking these services up into organizational units or modules
- Angular provides @NgModule decorator
- helps define what these modules look like
- and what they provide to our app
- keeps our app's bootstrap/launch time as fast as possible
- allows some service/features to be lazily loaded after our app has launched
High usability - core module ⌘
- low-level CoreModule - designing how we like to work with commonly used services, in a unique way
- across not only the app we are building now but more in the future
- can be easily moved into a completely different app
- and gain all the same unique APIs we have designed for this app when working with low-level services
Core module con't ⌘
cd src/app; mkdir modules; mkdir modules/core; touch modules/core/core.module.ts
// nativescript
import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
// angular
import { NgModule } from '@angular/core';
// app
import { PROVIDERS } from './services';
@NgModule({
imports: [
NativeScriptModule
],
providers: [
...PROVIDERS
],
exports: [
NativeScriptModule
]
})
export class CoreModule { }
modules/core/services/log.service.ts
// angular
import { Injectable } from '@angular/core';
@Injectable()
export class LogService {
}
modules/core/services/database.service.ts
// angular
import { Injectable } from '@angular/core';
@Injectable()
export class DatabaseService {
}
modules/core/services/index.ts
import { DatabaseService } from './database.service';
import { LogService } from './log.service';
export const PROVIDERS: any[] = [
DatabaseService,
LogService
];
export * from './database.service';
export * from './log.service';
Feature Modules ⌘
- focusing on just the unique abilities our app should provide outside of the CoreModule
- reducing the duplication of the code
- encourages and enhances rapid development
- injecting services provided by CoreModule and using those APIs
- Maintainability
- underlying detail needs to change - it need only be changed in one place (in the CoreModule service)
- no redundant code potentially spread across different sections of app
- Performance: Splitting into modules allows to load only the modules needed at startup
- then later, lazily load other features on demand (leads to a faster app startup time)
Feature Modules Con't ⌘
- PlayerModule - provides player-specific services and components (user is authenticated or not)
- RecorderModule - recording-specific services and components (user is authenticated and enters the record mode for the first time)
mkdir player; mkdir recorder; touch app/modules/player/player.module.ts
// nativescript
import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
// angular
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
@NgModule({
imports: [ NativeScriptModule ],
schemas: [ NO_ERRORS_SCHEMA ]
})
export class PlayerModule { }
touch app/modules/recorder/recorder.module.ts
// nativescript
import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
// angular
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
@NgModule({
imports: [ NativeScriptModule ],
schemas: [ NO_ERRORS_SCHEMA ]
})
export class RecorderModule { }
- Interface and model implementation for the core piece of data our app will be using
mkdir app/modules/core/models; touch app/modules/core/models/track.model.ts
export interface ITrack {
filepath?: string;
name?: string;
order?: number;
volume?: number;
solo?: boolean;
}
export class TrackModel implements ITrack {
public filepath: string;
public name: string;
public order: number;
public volume: number = 1; // set default to full volume
public solo: boolean;
constructor(model?: any) {
if (model) {
for (let key in model) {
this[key] = model[key];
}
}
}
}
Service APIs ⌘
API our services will provide to our app
- PlayerService - manages tracks and control playback
- RecorderService - provides a simple recording API
- LogService - helps debug as well as gain important insights into how app is used
- allows to reroute all the app logs somewhere else (for example TrackJS via Segment)
- DatabaseService - store for app's data, allows any type of data, handles JSON serialization
- provides static keys for data (might also provide static reference to a saved user)
- AuthService - provides static reference to a saved user
- certain components in our app may benefit from getting notified when the authenticated state changes
- utilizes RxJS library and simplifies dealing with changing data and events using observables
- An observable - used to listen to events, but also to filter, map, reduce, and run sequences of code against anytime something occurs
- Observables simplify asynchronous development - for example BehaviorSubject emits changes that our components could subscribe to
Player service ⌘
mkdir app/modules/player/services; touch app/modules/player/services/player.service.ts
// angular
import { Injectable } from '@angular/core';
// app
import { ITrack } from '../../core/models';
@Injectable()
export class PlayerService {
public playing: boolean;
public tracks: Array<ITrack>;
constructor() {
this.tracks = [];
}
public play(index: number): void {
this.playing = true;
}
public pause(index: number): void {
this.playing = false;
}
public addTrack(track: ITrack): void {
this.tracks.push(track);
}
public removeTrack(track: ITrack): void {
let index = this.getTrackIndex(track);
if (index > -1) {
this.tracks.splice(index, 1);
}
}
public reorderTrack(track: ITrack, newIndex: number) {
let index = this.getTrackIndex(track);
if (index > -1) {
this.tracks.splice(newIndex, 0, this.tracks.splice(index, 1)[0]);
}
}
private getTrackIndex(track: ITrack): number {
let index = -1;
for (let i = 0; i < this.tracks.length; i++) {
if (this.tracks[i].filepath === track.filepath) {
index = i;
break;
}
}
return index;
}
}
touch app/modules/player/services/index.ts
import { PlayerService } from './player.service';
export const PROVIDERS: any[] = [
PlayerService
];
export * from './player.service';
Update player.module.ts
//...
// app
import { PROVIDERS } from './services';
@NgModule({
//...
providers: [ ...PROVIDERS ],
//...
})
Recorder service ⌘
mkdir app/modules/recorder/services touch app/modules/recorder/services/recorder.service.ts
// angular
import { Injectable } from '@angular/core';
@Injectable()
export class RecorderService {
public record(): void { }
public stop(): void { }
}
touch app/modules/recorder/services/index.ts
import { RecorderService } from './recorder.service';
export const PROVIDERS: any[] = [
RecorderService
];
export * from './recorder.service';
Update recorder module:
//...
// app
import { PROVIDERS } from './services';
@NgModule({
//...
providers: [ ...PROVIDERS ],
//...
})
Logging service ⌘
Update file app/modules/core/services/log.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class LogService {
public static ENABLE: boolean = true;
public debug(msg: any, ...formatParams: any[]) {
if (LogService.ENABLE) {
console.log(msg, formatParams);
}
}
public error(msg: any, ...formatParams: any[]) {
if (LogService.ENABLE) {
console.error(msg, formatParams);
}
}
public inspect(obj: any) {
if (LogService.ENABLE) {
console.log(obj);
console.log('typeof: ', typeof obj);
if (obj) {
console.log('constructor: ', obj.constructor.name);
for (let key in obj) {
console.log(`${key}: `, obj[key]);
}
}
}
}
}
Database service ⌘
Update file app/modules/core/services/database.service.ts
// angular
import { Injectable } from '@angular/core';
// nativescript
import * as appSettings from 'application-settings';
interface IKeys {
currentUser: string;
}
@Injectable()
export class DatabaseService {
public static KEYS: IKeys = {
currentUser: 'current-user'
};
public setItem(key: string, value: any): void {
appSettings.setString(key, JSON.stringify(value));
}
public getItem(key: string): any {
let item = appSettings.getString(key);
if (item) {
return JSON.parse(item);
}
return item;
}
public removeItem(key: string): void {
appSettings.remove(key);
}
}
Authorization service ⌘
touch app/modules/core/services/auth.service.ts
// angular
import { Injectable } from '@angular/core';
// lib
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
// app
import { DatabaseService } from './database.service';
import { LogService } from './log.service';
@Injectable()
export class AuthService {
// access our current user from anywhere
public static CURRENT_USER: any;
// subscribe to authenticated state changes
public authenticated$: BehaviorSubject<boolean> =
new BehaviorSubject(false);
constructor(
private databaseService: DatabaseService,
private logService: LogService
) {
this._init();
}
private _init() {
AuthService.CURRENT_USER = this.databaseService
.getItem(DatabaseService.KEYS.currentUser);
this.logService.debug(`Current user: `,
AuthService.CURRENT_USER);
this._notifyState(!!AuthService.CURRENT_USER);
}
private _notifyState(auth: boolean) {
this.authenticated$.next(auth);
}
}
Update file app/modules/core/services/index.ts
import { AuthService } from './auth.service';
import { DatabaseService } from './database.service';
import { LogService } from './log.service';
export const PROVIDERS: any[] = [
AuthService,
DatabaseService,
LogService
];
export * from './auth.service';
export * from './database.service';
export * from './log.service';
Bootstrapping the whole thing ⌘
- Upfront - core module, player module
- Lazy loaded - recorder module
- Routing config
Update file app/app.component.ts
// angular
import { Component } from '@angular/core';
// app
import { AuthService } from './modules/core/services';
@Component({
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent {
constructor(private authService: AuthService) { }
}
Update file app/app.module.ts
// angular
import { NgModule } from '@angular/core';
// app
import { AppComponent } from './app.component';
import { CoreModule } from './modules/core/core.module';
import { PlayerModule } from './modules/player/player.module';
@NgModule({
imports: [
CoreModule,
PlayerModule
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
View with layout ⌘
- sdk examples
- nativescript-sdk-examples-ng-master/app/ng-ui-widgets-category/layouts/grid-layout
Schematics exercise ⌘
1. Installing the dependencies npm install -g @angular/cli@<version> # atm 7.2.4 npm install -g @nativescript/schematics<version> # atm ~0.4.0 npm install -g nativescript 2. Create a New Angular CLI Project with NativeScript Support ng new angu-proj cd angu-proj ng add @nativescript/schematics 2.1. Alternatively for fresh projects ng new --c=@nativescript/schematics --name=angu-proj --shared 3. Schematic Changes and Process for Angular Development 3.1. New '.tns.ts' and '.tns.html' files 3.2. Fix the 'app-routing.module.ts' with the necesary content from 'app-routing.module.tns.ts' 3.3. Adding new angular constructs ng g component about 3.3.1. Add about to the router 4. Running on desktop, Android (or iOS) ng serve tns platform add android tns run android --bundle 5. Shortcut: 1) ng new sample-project --collection @nativescript/schematics --shared 2) cd sample-project 3) npm install 4) ./node_modules/.bin/update-ns-webpack --configs 5) tns platform add android 6) tns update 7) npm audit fix 8) tns run --bundle # tns preview --bundle 9) ng serve
Using NativeScript Modules ⌘
- import NativeScriptModule (usually in our "core" module)
- our app will need it to
- work with other NativeScript for Angular features
- these features will be accessible globally for our app
- no need to worry about importing NativeScriptModule elsewhere
- our app will need it to
NS Modules con't ⌘
- Core modules
- Device functionality modules
- Data modules
- User interface modules
- Layouts
- Widgets
- WHATWG polyfills
Accessing media - location, camera, file ⌘
Accessing NativeScript's Animation APIs ⌘
- Exposes two ways
- Declarative - CSS3 animations API
- Imperative - calling animation methods directly with code
- Examples
Integrating a NativeScript Application with Native Android and IOS Apps ⌘
- Still experimental features
- Steps for android: https://docs.nativescript.org/angular/guides/integration-with-existing-ios-and-android-apps/extend-existing-android-app
- Steps for iOS: https://github.com/NativeScript/sample-ios-embedded
Unit Testing and Debugging the Application ⌘
- Tests
- Error handling
- Debugging
Error handling ⌘
- Differs from the usual web apps
- Devel server - with unhandled exception the app will crash, Error with the corresponding stack trace will be shown
- Production - instead of error log it is preferred to e.g. app freeze, blank screen, failed navigation
- Scenarios
- (dev) Throw exceptions as soon as an error occurs
- (dev) Show console.log with ERROR: Something bad happened but continue execution of the app
- (prod) Send an error report to your analytics/error-report server but continue app execution
- optionally triggers some recover logic that will handle the app without a crash
- More: https://docs.nativescript.org/angular/core-concepts/error-handling
Debugging ⌘
tns debug android tns debug android --bundle
- More: https://docs.nativescript.org/angular/tooling/debugging/debugging
- With chrome: https://docs.nativescript.org/angular/tooling/debugging/chrome-devtools
Testing ⌘
- Unit
- e2e
Testing con't - unit ⌘
- Unit
- NS supports Jasmine, Mocha with Chai and QUnit
- More: https://docs.nativescript.org/angular/tooling/testing/testing
tns test init tns test android # --emulator
Testing con't - e2e ⌘
- e2e
- testing application workflows and integration points
- NS supports Appium - open-source test automation framework
- nativescript-dev-appium - NS plugin
- More: https://docs.nativescript.org/angular/tooling/testing/end-to-end-testing/overview
Publishing the Application ⌘
- Android
- iOS
Android
- Launch screens
- Publishing
Troubleshooting ⌘
- Common problems: https://docs.nativescript.org/angular/troubleshooting
- Upgrading: https://docs.nativescript.org/angular/releases/upgrade-instructions