NativeScript

From Training Material
Jump to navigation Jump to search

THIS IS A DRAFT

This text may not be complete.


title
NativeScript with Angular 4
author
Lukasz Sokolowski

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 ⌘

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)

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

ns-common.png

  • Modules

ns-modules.png

  • Lifecycle

Life cycle ⌘

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 Multithreading con't ⌘

Workers.png

Creating a User Interface with NativeScript ⌘

NativeScript Ui

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 { }

Model shared for our data ⌘

  • 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

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 ⌘

Integrating a NativeScript Application with Native Android and IOS Apps ⌘

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

Testing ⌘

  • Unit
  • e2e

Testing con't - unit ⌘

tns test init
tns test android # --emulator

Testing con't - e2e ⌘

Publishing the Application ⌘

  • Android
  • iOS

Android

Troubleshooting ⌘

Summary and Conclusion ⌘