FrontendCon 2019 - @rowdyrabouw

Czy miałeś miły lunch?

Unleash your web skills on native!

Web developer mood coaster

web developer in natural habitat

web developer • HTML • CSS • JavaScript • Sass • Node Package Manager #FrontendCon 2019 - @rowdyrabouw 8/157

web developer on native iOS / Android

web developer & native • App stores • Provisioning files • Java or Kotlin for Android • Objective-C or Swift for iOS #FrontendCon 2019 - @rowdyrabouw 11/157

web developer with nativescript

web developer & nativescript • App stores • Provisioning files • NativeScript • HTML, CSS, JavaScript • Sass • Node Package Manager #FrontendCon 2019 - @rowdyrabouw 14/157

web developer ❤ nativescript

who?

Rowdy Rabouw » @rowdyrabouw » Gouda, The Netherlands » Freelance web and app developer » Lead developer Nationale-Nederlanden Pension App » Progress Developer Expert for Nativescript » I ❤ superhero movies #FrontendCon 2019 - @rowdyrabouw 18/157

Mobile App framework decision guide

Mobile App framework decision guide Do you want/need a native User Interface and native performance? No #FrontendCon 2019 - @rowdyrabouw Yes 23/157

Mobile App framework decision guide Do you want/need a native User Interface and native performance? No Yes Phonegap / Cordova with Ionic • WebView • DOM to manipulate • HTML styled like native #FrontendCon 2019 - @rowdyrabouw 24/157

Mobile App framework decision guide Do you want/need a native User Interface and native performance? No Phonegap / Cordova with Ionic Yes continue • WebView • DOM to manipulate • HTML styled like native #FrontendCon 2019 - @rowdyrabouw 25/157

Mobile App framework decision guide Do you have too much money and time? Yes #FrontendCon 2019 - @rowdyrabouw No 26/157

Mobile App framework decision guide Do you have too much money and time? Yes No Native iOS and Android • Twice the work #FrontendCon 2019 - @rowdyrabouw 27/157

Mobile App framework decision guide Do you have too much money and time? Yes Native iOS and Android No continue • Twice the work #FrontendCon 2019 - @rowdyrabouw 28/157

Mobile App framework decision guide Do you potentially want/need to share code with the web? Or do you want/need to use web technologies? No #FrontendCon 2019 - @rowdyrabouw Yes 29/157

Mobile App framework decision guide Do you potentially want/need to share code with the web? Or do you want/need to use web technologies? No Yes Xamarin • .NET or C# • Cross compiling • Bindings to access native APIs #FrontendCon 2019 - @rowdyrabouw 30/157

Mobile App framework decision guide Do you potentially want/need to share code with the web? Or do you want/need to use web technologies? No Yes Flutter • Dart • Cross compiling #FrontendCon 2019 - @rowdyrabouw 31/157

Mobile App framework decision guide Do you potentially want/need to share code with the web? Or do you want/need to use web technologies? No Flutter Yes continue • Dart • Cross compiling #FrontendCon 2019 - @rowdyrabouw 32/157

Mobile App framework decision guide Do you want to use modern JavaScript? No #FrontendCon 2019 - @rowdyrabouw Yes 33/157

Mobile App framework decision guide Do you want to use modern JavaScript? No Yes Titanium • No ES6/ES2015 support • Can’t use NPM • Old MVC framework (Alloy) #FrontendCon 2019 - @rowdyrabouw 34/157

Mobile App framework decision guide Do you want to use modern JavaScript? No Titanium Yes continue • No ES6/ES2015 support • Can’t use NPM • Old MVC framework (Alloy) #FrontendCon 2019 - @rowdyrabouw 35/157

Mobile App framework decision guide Do you know and like React? Yes #FrontendCon 2019 - @rowdyrabouw No 36/157

Mobile App framework decision guide Do you know and like React? Yes No React Native • React • Bridge to access native APIs #FrontendCon 2019 - @rowdyrabouw 37/157

Mobile App framework decision guide Do you know and like React? Yes No React Native • React • Bridge to access native APIs • Hold my beer! #FrontendCon 2019 - @rowdyrabouw 38/157

Mobile App framework decision guide Do you know and like React? Yes React Native No continue • React • Bridge to access native APIs • Hold my beer! #FrontendCon 2019 - @rowdyrabouw 39/157

NATIVESCRIPT

NATIVESCRIPT

What is NativeScript? • Open source framework for building truly native mobile apps • JavaScript, markup (XML/HTML) and CSS • Native code inside your JavaScript if you want and dare • Cross Platform: one codebase for iOS and Android • Backed by software company Progress • Android 4.2 or a later stable official release • iOS 9.0 or later stable official release #FrontendCon 2019 - @rowdyrabouw 43/157

docs.nativescript.org

nativescript.org/get-the-nativescript-book

nativescripting.com

nativescriptcommunity.slack.com

nativescript.org/nativescript-sidekick

play.nativescript.org

markup like on the web

Markup http://2xr.nl/markup https://docs.nativescript.org/ui/components <ActionBar title=”Native elements”/> <StackLayout> <Button text=”Button” tap=”{{ onButtonTap }}”/> <Switch checked=”false”/> <SegmentedBar items=”{{ segmentedBarItems }}”/> <Progress value=”0” maxValue=”100”/> <Slider value=”0” minValue=”0” maxValue=”100”/> <DatePicker year=”2018” month=”1” day=”1” minDate=”1970-01-01” maxDate=”2100-12-31”/> </StackLayout> #FrontendCon 2019 - @rowdyrabouw 52/157

TextField https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield <TextField/> <TextField text=”“/> <TextField hint=”Enter your name”/> #FrontendCon 2019 - @rowdyrabouw 54/157

TextField: autocapitalization https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield <TextField autocapitalizationType=”allCharacters”/> <TextField autocapitalizationType=”sentences”/> <TextField autocapitalizationType=”words”/> #FrontendCon 2019 - @rowdyrabouw 55/157

TextField: autocapitalization https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield <TextField autocapitalizationType=”allCharacters”/> <TextField autocapitalizationType=”sentences”/> <TextField autocapitalizationType=”words”/> <TextField autocapitalizationType=”none”/> #FrontendCon 2019 - @rowdyrabouw 56/157

TextField: autocorrect https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield <TextField autocorrect=”true”/> <TextField autocorrect=”false”/> #FrontendCon 2019 - @rowdyrabouw 57/157

TextField: keyboardType http://2xr.nl/keyboardType https://docs.nativescript.org/api-reference/modules/ui_enums.keyboardtype <TextField keyboardType=”number”/> <TextField keyboardType=”datetime”/> <TextField keyboardType=”phone”/> <TextField keyboardType=”email”/> <TextField keyboardType=”url”/> #FrontendCon 2019 - @rowdyrabouw 58/157

TextField: more attributes https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield <TextField textAlignment=”“/> <TextField visibility=”“/> <TextField width=”“/> <TextField maxLength=”“/> #FrontendCon 2019 - @rowdyrabouw 60/157

Layouts https://docs.nativescript.org/ui/layouts #FrontendCon 2019 - @rowdyrabouw 61/157

Layouts https://docs.nativescript.org/ui/layouts #FrontendCon 2019 - @rowdyrabouw 62/157

DockLayout https://docs.nativescript.org/ui/layouts <DockLayout height=”100%” stretchLastChild=”true”> <Label <Label <Label <Label text=”1” text=”2” text=”3” text=”4” dock=”top”/> dock=”bottom”/> dock=”right”/> dock=”left”/> </DockLayout> <!-1 + 2 have fixed height 3 has fixed width 4 will get all remaining space —> #FrontendCon 2019 - @rowdyrabouw 63/157

GridLayout https://docs.nativescript.org/ui/layouts <GridLayout rows=”100, auto, *” columns=”100, auto, *”> <Label text=”1” row=”0” <Label text=”2” row=”0” colSpan=”2”/> <Label text=”3” row=”1” rowSpan=”2”/> <Label text=”4” row=”1” <Label text=”5” row=”1” <Label text=”6” row=”2” <Label text=”7” row=”2” </GridLayout> #FrontendCon 2019 - @rowdyrabouw col=”0”> col=”1” col=”0” col=”1”/> col=”2”/> col=”1”/> col=”2”/> 64/157

Cascading Style Sheets

Cascading Style Sheets https://docs.nativescript.org/ui/styling • a large subset of CSS properties is supported • device-independent pixels • application-wide, page-specific or inline • platform-specific possible • animations • SASS #FrontendCon 2019 - @rowdyrabouw 67/157

{N} Core Themes https://docs.nativescript.org/ui/theme • ready to use color schemes • tailored for iOS and Android #FrontendCon 2019 - @rowdyrabouw

NativeScript Theme Builder https://www.nativescriptthemebuilder.com #FrontendCon 2019 - @rowdyrabouw 69/157

TabView <TabView height=”100%”> <StackLayout *tabItem=”{title: ‘Rocket Raccoon’}” class=”full rocket”/> <StackLayout *tabItem=”{title: ‘Harley Quinn’}” class=”full harley”/> <StackLayout *tabItem=”{title: ‘Hulk’}” class=”full hulk”/> </TabView> #FrontendCon 2019 - @rowdyrabouw 70/157

TabView .full { background-size: cover; background-position: center; background-repeat: no-repeat; } .rocket { background-image: url(“~/images/rocket-raccoon.jpg”); } .harley { background-image: url(“~/images/harley-quinn.jpg”); } .hulk { background-image: url(“~/images/hulk.jpg”); } #FrontendCon 2019 - @rowdyrabouw 71/157

4 Fantastic Choices

6 Big Heroes

JavaScript

TypeScript

Angular

Vue.js Igor Randjelovic (NativeScript-Vue)

Svelte David Pershouse (Svelte Native)

React Jamie Birch (React NativeScript)

Native Code Objective-C

{N} Angular directory structure app/components/slider/ slider.component.html slider.component.css slider.component.ts slider-routing.module.ts slider-module.ts #FrontendCon 2019 - @rowdyrabouw 85/157

slider.component.html <ActionBar> <NavigationButton visibility=”collapsed”></NavigationButton> </ActionBar> <StackLayout> <Slider></Slider> </StackLayout> #FrontendCon 2019 - @rowdyrabouw 86/157

slider.component.css ActionBar { background-color: #FFFFFF; } StackLayout { padding: 50; } #FrontendCon 2019 - @rowdyrabouw 87/157

slider.component.ts import { Component } from “@angular/core”; @Component({ selector: “app-slider”, moduleId: module.id, templateUrl: “slider.component.html”, styleUrls: [“slider.component.css”] }) export class SliderComponent { constructor() {} } #FrontendCon 2019 - @rowdyrabouw 88/157

slider-routing.module.ts import { NgModule } from “@angular/core”; import { Routes } from “@angular/router”; import { NativeScriptRouterModule } from “nativescript-angular/router”; import { SliderComponent } from “./slider.component”; const routes: Routes = [{ path: “”, component: SliderComponent }]; @NgModule({ imports: [NativeScriptRouterModule.forChild(routes)], exports: [NativeScriptRouterModule] }) export class SliderRoutingModule {} #FrontendCon 2019 - @rowdyrabouw 89/157

slider.module.ts import { NgModule, NO_ERRORS_SCHEMA } from “@angular/core”; import { NativeScriptModule } from “nativescript-angular/nativescript.module”; import { SliderRoutingModule } from “./slider-routing.module”; import { SliderComponent } from “./slider.component”; @NgModule({ imports: [NativeScriptModule, SliderRoutingModule], declarations: [SliderComponent], schemas: [NO_ERRORS_SCHEMA] }) export class SliderModule {} #FrontendCon 2019 - @rowdyrabouw 90/157

#FrontendCon 2019 - @rowdyrabouw 91/157

slider.component.html <ActionBar> <NavigationButton visibility=”collapsed”></NavigationButton> </ActionBar> <StackLayout> <Slider slider-icon></Slider> </StackLayout> • attribute directive • changes the appearance or behavior of an element #FrontendCon 2019 - @rowdyrabouw 92/157

slider.directive.ts import { Directive, ElementRef } from “@angular/core”; import { isIOS } from “platform”; @Directive({ selector: “[slider-icon]” }) export class SliderIconDirective { constructor(private el: ElementRef) { if (isIOS) { const uiSlider = this.el.nativeElement.ios; uiSlider.setThumbImageForState( UIImage.imageNamed(“image.png”), UIControlState.Normal); } } } #FrontendCon 2019 - @rowdyrabouw 93/157

slider.directive.ts import { Directive, ElementRef } from “@angular/core”; import { isIOS } from “platform”; @Directive({ selector: “[slider-icon]” }) export class SliderIconDirective { constructor(private el: ElementRef) { if (isIOS) { const uiSlider = this.el.nativeElement.ios; uiSlider.setThumbImageForState( UIImage.imageNamed(“image.png”), UIControlState.Normal); } } } #FrontendCon 2019 - @rowdyrabouw 94/157

slider.directive.ts import { Directive, ElementRef } from “@angular/core”; import { isIOS } from “platform”; @Directive({ selector: “[slider-icon]” }) export class SliderIconDirective { constructor(private el: ElementRef) { if (isIOS) { const uiSlider = this.el.nativeElement.ios; uiSlider.setThumbImageForState( UIImage.imageNamed(“image.png”), UIControlState.Normal); } } } #FrontendCon 2019 - @rowdyrabouw 95/157

slider.module.ts import { NgModule, NO_ERRORS_SCHEMA } from “@angular/core”; import { NativeScriptModule } from “nativescript-angular/nativescript.module”; import { SliderIconDirective } from “./slider.directive”; import { SliderRoutingModule } from “./slider-routing.module”; import { SliderComponent } from “./slider.component”; @NgModule({ imports: [NativeScriptModule, SliderRoutingModule], declarations: [SliderComponent, SliderIconDirective], schemas: [NO_ERRORS_SCHEMA] }) export class SliderModule {} #FrontendCon 2019 - @rowdyrabouw 96/157

#FrontendCon 2019 - @rowdyrabouw 97/157

slider.component.html <ActionBar> <NavigationButton visibility=”collapsed”></NavigationButton> </ActionBar> <StackLayout> <AbsoluteLayout> <StackLayout #background class=”captain” top=”0” left=”0”></StackLayout> <FlexboxLayout class=”flexcontainer” top=”0” left=”0”> <Slider slider-icon (valueChange)=”onSliderChange($event)”></Slider> </FlexboxLayout> </AbsoluteLayout> </StackLayout> #FrontendCon 2019 - @rowdyrabouw 98/157

slider.component.css .captain { background-image: url(“~/assets/images/captain-america.jpg”); background-repeat: no-repeat; background-position: center; background-size: cover; height: 100%; width: 100%; opacity: 0; } #FrontendCon 2019 - @rowdyrabouw 99/157

slider.component.css .flexcontainer { justify-content: center; align-items: center; height: 100%; width: 100%; } Slider { width: 80%; background-color: #BC1A0F; } #FrontendCon 2019 - @rowdyrabouw 100/157

slider.component.ts import import import import import { { { { { Component, OnInit, ViewChild, ElementRef } from “@angular/core”; Page } from “ui/page”; Slider } from “ui/slider”; StackLayout } from “ui/layouts/stack-layout”; TNSPlayer } from “nativescript-audio”; @Component({ selector: “app-slider”, moduleId: module.id, templateUrl: “slider.component.html”, styleUrls: [“slider.component.css”] }) #FrontendCon 2019 - @rowdyrabouw 101/157

slider.component.ts export class SliderComponent implements OnInit { @ViewChild(“background”) background: ElementRef; private viewStack: StackLayout; private player: TNSPlayer; constructor(private page: Page) {} ngOnInit() { this.page.actionBarHidden = true; this.viewStack = this.background.nativeElement; this.player = new TNSPlayer(); this.player.initFromFile({ audioFile: “~/assets/audio/captain.mp3”, loop: false }); } #FrontendCon 2019 - @rowdyrabouw 102/157

slider.component.ts onSliderValueChange(args) { let slider = <Slider>args.object; // opacity and volume range is 0 - 1 let sliderValue = slider.value / 100; this.viewStack.opacity = sliderValue; if (Math.round(slider.value) > 0) { this.player.play(); this.player.volume = sliderValue; } else { this.player.seekTo(0); this.player.pause(); } } } #FrontendCon 2019 - @rowdyrabouw 103/157

Packages & Libraries

Node Package Manager • commonly known as npm • ready to use JavaScript modules • about 650.000 packages of free, reusable code #FrontendCon 2019 - @rowdyrabouw 106/157

Android Arsenal • libraries for Android (Java / Kotlin) Cocoapods • libraries for iOS (Objective-C / Swift) #FrontendCon 2019 - @rowdyrabouw 107/157

ngx-translate

Multilingual: ngx-translate • internationalization library for Angular 2+ • define translations in different languages • switch between them easily • no hardcoded text/labels, all in one place • start directly, even with one language #FrontendCon 2019 - @rowdyrabouw 110/157

#FrontendCon 2019 - @rowdyrabouw 111/157

Multilingual: ngx-translate { “CHART”: { “BUTTONS”: { “SALARY”: “Salaris / inkomen”, “PENSIONS”: “Pensioenen”, “CHOICES”: “Keuzes voor later”, }, }, } #FrontendCon 2019 - @rowdyrabouw 112/157

Multilingual: ngx-translate { “CHART”: { “BUTTONS”: { “SALARY”: “Salary / income”, “PENSIONS”: “Pensions”, “CHOICES”: “Choices for later”, }, }, } #FrontendCon 2019 - @rowdyrabouw 113/157

Multilingual: ngx-translate <Button [text]=“‘CHART.BUTTONS.SALARY’ | translate” ></Button> <Button [text]=“‘CHART.BUTTONS.PENSIONS’ | translate”></Button> <Button [text]=“‘CHART.BUTTONS.CHOICES’ | translate”></Button> [] = one way data binding in Angular | = display-value transformations #FrontendCon 2019 - @rowdyrabouw 114/157

NativeScript Plugins

What are {N} plugins? When the NativeScript core modules do not provide the native device or platform capability that you need, you can use plugins. • usually for both iOS and Android • JavaScript interface to native platform code #FrontendCon 2019 - @rowdyrabouw 117/157

version-number plugin

version-number plugin » version-number.d.ts » version-number.ios.ts » version-number.android.ts » package.json #FrontendCon 2019 - @rowdyrabouw 119/157

version-number.d.ts export declare class VersionNumber { constructor(); getVersion(): string; } #FrontendCon 2019 - @rowdyrabouw 120/157

version-number.ios.ts declare let NSBundle: any; export class VersionNumber { constructor() {} getVersion(): string { let version = NSBundle.mainBundle.objectForInfoDictionaryKey( “CFBundleShortVersionString” ); return version; } } #FrontendCon 2019 - @rowdyrabouw 121/157

version-number.ios.ts declare let NSBundle: any; export class VersionNumber { constructor() {} getVersion(): string { let version = NSBundle.mainBundle.objectForInfoDictionaryKey( “CFBundleShortVersionString” ); return version; } } #FrontendCon 2019 - @rowdyrabouw 122/157

NSBundle NSView, NSStackView, NSButton, NSImageView, NSSwitch, NSMenu, NSStatusBar, NSPanel, NSWindowTab, NSAlert, NSColorPicker, NSAnimationEffect, NSSpeechRecognizer, NSHapticFeedbackPerformer, … #FrontendCon 2019 - @rowdyrabouw 123/157

NeXTSTEP side step

version-number.android.ts import * as application from “tns-core-modules/application”; declare let android: any; declare let java: any; export class VersionNumber { constructor() {} getVersion(): string { let PackageManager = android.content.pm.PackageManager; let pkg = application.android.context .getPackageManager() .getPackageInfo( application.android.context.getPackageName(), PackageManager.GET_META_DATA ); return java.lang.Integer.toString(pkg.versionCode); } } #FrontendCon 2019 - @rowdyrabouw 130/157

version-number.android.ts import * as application from “tns-core-modules/application”; declare let android: any; declare let java: any; export class VersionNumber { constructor() {} getVersion(): string { let PackageManager = android.content.pm.PackageManager; let pkg = application.android.context .getPackageManager() .getPackageInfo( application.android.context.getPackageName(), PackageManager.GET_META_DATA ); return java.lang.Integer.toString(pkg.versionCode); } } #FrontendCon 2019 - @rowdyrabouw 131/157

AndroidManifest.xml <?xml version=”1.0” encoding=”utf-8”?> <manifest xmlns:android=”http://schemas.android.com/apk/res/android” android:versionCode=”2”> …. </manifest> #FrontendCon 2019 - @rowdyrabouw 132/157

package.json { “name”: “nativescript-version-number”, “version”: “1.0.0”, “main”: “version-number”, “typings”: “version-number.d.ts”, “nativescript”: { “platforms”: { “android”: “6.0.0”, “ios”: “6.0.1” } } …. } #FrontendCon 2019 - @rowdyrabouw 133/157

Install # install from NPM tns plugin add nativescript-version-number # install from local tns plugin add ../nativescript-version-number #FrontendCon 2019 - @rowdyrabouw 134/157

usage import { VersionNumber } from “nativescript-version-number”; const version = new VersionNumber().getVersion(); #FrontendCon 2019 - @rowdyrabouw 135/157

PLUGIN BOILERPLATE

Nativescript Marketplace #FrontendCon 2019 - @rowdyrabouw 140/157

Nativescript Marketplace #FrontendCon 2019 - @rowdyrabouw 141/157

Nativescript Marketplace #FrontendCon 2019 - @rowdyrabouw 142/157

{N} Plugins demo

#FrontendCon 2019 - @rowdyrabouw 144/157

#FrontendCon 2019 - @rowdyrabouw 145/157

#FrontendCon 2019 - @rowdyrabouw 146/157

Philips Hue 147/157

#FrontendCon 2019 - @rowdyrabouw 148/157

#FrontendCon 2019 - @rowdyrabouw 149/157

nativescript-bluetooth import * as bluetooth from “nativescript-bluetooth”; bluetooth.startScanning({ seconds: 4, onDiscovered: peripheral => { if (peripheral.UUID == “CA9F644C-1920-4572-8833-1D137A6T2A05”) { bluetooth.connect({ UUID: peripheral.UUID, onConnected: peripheral => { bluetooth.stopScanning(); // do stuff } }); } } }); #FrontendCon 2019 - @rowdyrabouw 150/157

nativescript-accelerometer import { startAccelerometerUpdates } from “nativescript-accelerometer”; startAccelerometerUpdates(data => { // lean left (0 to -1) / right (0 to 1) let leftOrRight = data.x; // lean forward (0 to -1) / back (0 to 1) let forwardOrBack = data.y; // do stuff }); #FrontendCon 2019 - @rowdyrabouw 151/157

github.com EddyVerbruggen nativescript-pluginshowcase

#FrontendCon 2019 - @rowdyrabouw 153/157

2xr.nl/fc2019 #FrontendCon 2019 - @rowdyrabouw

Dziękuję bardzo! #FrontendCon 2019 - @rowdyrabouw

#FrontendCon 2019 - @rowdyrabouw