A presentation at OdessaJS in in Odesa, Odessa Oblast, Ukraine, 65000 by Rowdy Rabouw
OdessaJS 2018 - @rowdyrabouw
Unleash your web skills on native!
Web developer mood coaster
web developer in natural habitat
web developer • HTML • CSS • JavaScript • Sass • Node Package Manager #OdessaJS - @rowdyrabouw 7/129
web developer on native iOS / Android
web developer & native • App stores • Provisioning files • Java or Kotlin for Android • Objective-C or Swift for iOS #OdessaJS - @rowdyrabouw 10/129
web developer with nativescript
web developer & nativescript • App stores • Provisioning files • NativeScript • HTML, CSS, JavaScript • Sass • Node Package Manager #OdessaJS - @rowdyrabouw 13/129
web developer ❤ nativescript
who?
Rowdy Rabouw @rowdyrabouw Gouda, The Netherlands I ❤ superhero movies Freelance web and app developer Lead developer Nationale-Nederlanden Pension App Progress Developer Expert for Nativescript #OdessaJS - @rowdyrabouw 17/129
Mobile App framework decision guide
Mobile App framework decision guide Do you want/need a native User Interface and native performance? No Yes #OdessaJS - @rowdyrabouw 20/129
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 #OdessaJS - @rowdyrabouw 21/129
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 continue #OdessaJS - @rowdyrabouw 22/129
Mobile App framework decision guide Do you have too much money and time? Yes No #OdessaJS - @rowdyrabouw 23/129
Mobile App framework decision guide Do you have too much money and time? Yes No Native iOS and Android • Twice the work #OdessaJS - @rowdyrabouw 24/129
Mobile App framework decision guide Do you have too much money and time? Yes No Native iOS and Android • Twice the work continue #OdessaJS - @rowdyrabouw 25/129
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 #OdessaJS - @rowdyrabouw 26/129
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 #OdessaJS - @rowdyrabouw 27/129
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 • Beta #OdessaJS - @rowdyrabouw 28/129
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 • Beta continue #OdessaJS - @rowdyrabouw 29/129
Mobile App framework decision guide Do you want to use modern JavaScript? No Yes #OdessaJS - @rowdyrabouw 30/129
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) #OdessaJS - @rowdyrabouw 31/129
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) continue #OdessaJS - @rowdyrabouw 32/129
Mobile App framework decision guide Do you know and like React? Yes No #OdessaJS - @rowdyrabouw 33/129
Mobile App framework decision guide Do you know and like React? Yes No React Native • React • Native code to access APIs • Version 0.55 #OdessaJS - @rowdyrabouw 34/129
Mobile App framework decision guide Do you know and like React? Yes No React Native • React • Native code to access APIs • Version 0.55 continue #OdessaJS - @rowdyrabouw 35/129
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 7.0 or later stable official release #OdessaJS - @rowdyrabouw 39/129
Nice ... but get me up to speed
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install node
npm install -g nativescript
@powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://www.nativescript.org/setup/win'))"
ruby -e "$(curl -fsSL https://www.nativescript.org/setup/mac)" #OdessaJS - @rowdyrabouw 42/129
tns <Command> [Command Parameters] [-- command <Options>] tns create <app-name> tns create <app-name> --tsc tns create <app-name> --ng vue init nativescript-vue/vue-cli-template <app-name> tns create <app-name> --template <template-url> tns platform add ios tns platform add android tns build ios tns build android tns run ios tns run android #OdessaJS - @rowdyrabouw 43/129
docs.nativescript.org
nativescript.org/get-the-nativescript-book
nativescripting.com
nativescript.org/nativescript-sidekick
play.nativescript.org
forum.nativescript.org
nativescriptcommunity.slack.com
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
#OdessaJS - @rowdyrabouw 53/129
TextField https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield < TextField /> < TextField
text =""/> < TextField
hint ="Enter your name"/> #OdessaJS - @rowdyrabouw 55/129
TextField: autocapitalization https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield < TextField
autocapitalizationType ="allCharacters"/> < TextField
autocapitalizationType ="sentences"/> < TextField
autocapitalizationType ="words"/> #OdessaJS - @rowdyrabouw 56/129
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"/> #OdessaJS - @rowdyrabouw 57/129
TextField: autocorrect https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield < TextField
autocorrect ="true"/> < TextField
autocorrect ="false"/> #OdessaJS - @rowdyrabouw 58/129
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"/> #OdessaJS - @rowdyrabouw 59/129
TextField: more attributes https://docs.nativescript.org/api-reference/modules/ui_text_field.textfield < TextField
textAlignment =""/> < TextField
visibility =""/> < TextField
width =""/> < TextField
maxLength =""/> #OdessaJS - @rowdyrabouw 61/129
Layouts https://docs.nativescript.org/ui/layouts #OdessaJS - @rowdyrabouw 62/129
Layouts https://docs.nativescript.org/ui/layouts #OdessaJS - @rowdyrabouw 63/129
DockLayout https://docs.nativescript.org/ui/layouts < DockLayout
height ="100%"
stretchLastChild ="true">
< Label
text ="1" dock ="top"/>
< Label
text ="2" dock ="bottom"/>
< Label
text ="3" dock ="right"/>
< Label
text ="4" dock ="left"/> </ DockLayout
<!-- 1 + 2 have fixed height 3 has fixed width 4 will get all remaining space -->#OdessaJS - @rowdyrabouw 64/129
GridLayout https://docs.nativescript.org/ui/layouts < GridLayout
rows ="100, auto, *"
columns ="100, auto, *">
< Label
text ="1" row ="0" col ="0">
< Label
text ="2" row ="0" col ="1"
colSpan ="2"/>
< Label
text ="3" row ="1" col ="0"
rowSpan ="2"/>
< Label
text ="4" row ="1" col ="1"/>
< Label
text ="5" row ="1" col ="2"/>
< Label
text ="6" row ="2" col ="1"/>
< Label
text ="7" row ="2" col ="2"/> </ GridLayout
#OdessaJS - @rowdyrabouw 65/129
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 #OdessaJS - @rowdyrabouw 68/129
{N} Core Themes https://docs.nativescript.org/ui/theme • ready to use color schemes • tailored for iOS and Android #OdessaJS - @rowdyrabouw
NativeScript Theme Builder https://www.nativescriptthemebuilder.com #OdessaJS - @rowdyrabouw 70/129
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
#OdessaJS - @rowdyrabouw 71/129
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" ); } #OdessaJS - @rowdyrabouw 72/129
Fantastic 4 Choices
4 Fantastic Choices
JavaScript
TypeScript
Vue.js
Angular
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 #OdessaJS - @rowdyrabouw 83/129
slider.component.html < StackLayout
< Slider
</ Slider
</ StackLayout
#OdessaJS - @rowdyrabouw 84/129
slider.component.css StackLayout {
padding : 50 ; } #OdessaJS - @rowdyrabouw 85/129
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 () {} } #OdessaJS - @rowdyrabouw 86/129
#OdessaJS - @rowdyrabouw 87/129
slider.component.html < StackLayout
< Slider
slider-icon
</ Slider
</ StackLayout
• attribute directive • changes the appearance or behavior of an element #OdessaJS - @rowdyrabouw 88/129
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) {
let uiSlider = this .el.nativeElement.ios; uiSlider.setThumbImageForState( UIImage.imageNamed( "image.png" ), UIControlState.Normal); } } } #OdessaJS - @rowdyrabouw 89/129
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) {
let uiSlider = this .el.nativeElement.ios; uiSlider.setThumbImageForState( UIImage.imageNamed( "image.png" ), UIControlState.Normal); } } } #OdessaJS - @rowdyrabouw 90/129
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) {
let uiSlider = this .el.nativeElement.ios; uiSlider.setThumbImageForState( UIImage.imageNamed( "image.png" ), UIControlState.Normal); } } } #OdessaJS - @rowdyrabouw 91/129
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 {} #OdessaJS - @rowdyrabouw 92/129
93/129
slider.component.html < 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
#OdessaJS - @rowdyrabouw 94/129
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 ; } #OdessaJS - @rowdyrabouw 95/129
slider.component.css .flexcontainer {
justify-content : center;
align-items : center;
height : 100% ;
width : 100% ; } Slider {
width : 80% ;
background-color : #BC1A0F ; } ActionBar {
background-color : #FFFFFF ; } #OdessaJS - @rowdyrabouw 96/129
slider.component.ts import { Component, OnInit, ViewChild, ElementRef } from
"@angular/core" ; import { Page } from
"ui/page" ; import { Slider } from
"ui/slider" ; import { StackLayout } from
"ui/layouts/stack-layout" ; import { TNSPlayer } from
"nativescript-audio" ; @Component({
selector : "app-slider" ,
moduleId : module .id,
templateUrl : "slider.component.html" ,
styleUrls : [ "slider.component.css" ] }) #OdessaJS - @rowdyrabouw 97/129
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 }); } #OdessaJS - @rowdyrabouw 98/129
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(); } } } #OdessaJS - @rowdyrabouw 99/129
Packages & Libraries
Node Package Manager • commonly known as npm • ready to use JavaScript modules • about 650.000 packages of free, reusable code #OdessaJS - @rowdyrabouw 102/129
Android Arsenal • libraries for Android (Java / Kotlin) Cocoapods • libraries for iOS (Objective-C / Swift) #OdessaJS - @rowdyrabouw 103/129
Multilingual: 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 #OdessaJS - @rowdyrabouw 106/129
Multilingual: ngx-translate {
"HOME" : {
"TITLE" : "Hello OdessaJS!" ,
"TEXT" : "It's great to be here and introduce NativeScript to you." ,
"SLIDER" : "Slider" ,
"ENGLISH" : "English" ,
"FOREIGN" : "Ukrainian" ,
"MIP" : "WowWee MiP" ,
"SPEECH_RECOGNITION" : "Speech recognition!" } } #OdessaJS - @rowdyrabouw 107/129
Multilingual: ngx-translate < ActionBar
[ title ]="'HOME.TITLE' | translate"> </ ActionBar
< Button
[ text ]="'HOME.FOREIGN' | translate" ( tap )="changeLanguage('uk')"> </ Button
[] = one way data binding in Angular | = display-value transformations #OdessaJS - @rowdyrabouw 108/129
109/129
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 #OdessaJS - @rowdyrabouw 112/129
#OdessaJS - @rowdyrabouw 113/129
Nativescript plugins • nativescript-bluetooth • nativescript-accelerometer • nativescript-speech-recognition • nativescript-texttospeech #OdessaJS - @rowdyrabouw 114/129
Nativescript plugins • nativescript-directions • nativescript-camera • nativescript-social-share • nativescript-videoplayer #OdessaJS - @rowdyrabouw 115/129
{N} Plugins live demo
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 } }); } } }); #OdessaJS - @rowdyrabouw 117/129
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 }); #OdessaJS - @rowdyrabouw 118/129
nativescript-speech-recognition import { SpeechRecognition, SpeechRecognitionTranscription }
from
"nativescript-speech-recognition" ; private speechRecognition = new SpeechRecognition(); this .speechRecognition.available().then(
( available: boolean ) =>
console .log(available ? "YES!" : "NO" ), (err: string) => console .log(err) ); this .speechRecognition.requestPermission().then( ( granted: boolean ) => {
console .log( "Granted? "
nativescript-speech-recognition this .speechRecognition.startListening({
locale : "en-US" ,
returnPartialResults : true ,
onResult : ( transcription: SpeechRecognitionTranscription ) => {
console
.log(
User said: ${transcription.text}
);
}
});
this
.speechRecognition.stopListening().then(
()
=>
{
// do something with the recognized text }); #OdessaJS - @rowdyrabouw 120/129
nativescript-texttospeech import { TNSTextToSpeech, SpeakOptions }
from
"nativescript-texttospeech" ; let textToSpeech = new TNSTextToSpeech(); let speakOptions: SpeakOptions = {
text : "Hello world!" ,
locale : "en-US" ,
speakRate : 0.5 ,
pitch : 0.8 }; textToSpeech.speak(speakOptions); . #OdessaJS - @rowdyrabouw 121/129
nativescript-directions import { Directions } from
"nativescript-directions" ; let directions = new Directions(); directions.navigate({
// optional, default "current location"
from : {
lat : 46.433131 ,
lng : 30.7618338 },
// if an Array is passed, the last item is the destination,
// the addresses in between are "waypoints" to: [{
address : "Vorontsovs'kyi Ln, 4, Odessa" , // Witch House }, {
address : "Potemkin Stairs, Odesa, Ukraine" }],
ios : {
preferGoogleMaps : true ,
allowGoogleMapsWeb : true
} }) #OdessaJS - @rowdyrabouw 122/129
nativescript-camera import * as camera from
"nativescript-camera" ; import { ImageSource } from
"tns-core-modules/image-source" ; camera.requestPermissions(); camera.takePicture({
width : 1000 ,
height : 1000 }) .then( imageAsset => {
new ImageSource().fromAsset(imageAsset).then( imageSource => {
// do something with the image }); }); #OdessaJS - @rowdyrabouw 123/129
nativescript-social-share import * as camera from
"nativescript-camera" ; import { ImageSource } from
"tns-core-modules/image-source" ; import * as SocialShare from
"nativescript-social-share" ; camera.requestPermissions(); camera.takePicture({
width : 1000 ,
height : 1000 }) .then( imageAsset => {
new ImageSource().fromAsset(imageAsset).then( imageSource => { SocialShare.shareImage(imageSource); }); }); #OdessaJS - @rowdyrabouw 124/129
nativescript-videoplayer < VideoPlayer
src ="video.mp4"
controls ="true"
loop ="true"
autoplay ="false"> </ VideoPlayer
#OdessaJS - @rowdyrabouw 125/129
2xr.nl/odessajs #OdessaJS - @rowdyrabouw
Дякую #OdessaJS - @rowdyrabouw
Links Native elements playground: http://2xr.nl/markup keyboardType playground: http://2xr.nl/keyboardType TabView playground: http://2xr.nl/TabView Plugin demo app: https://github.com/rowdyrabouw/NativeScriptTalk/tree/OdessaJS #OdessaJS - @rowdyrabouw 129/129
Did you know you can use your knowledge of HTML, CSS and JavaScript to build truly native apps for iOS and Android with NativeScript? I’ll explain what NativeScript is, how it compares to other frameworks and demo how easy and fun it is to get started and how to make use of native capabilities. Do you want to build your own personal assistant like Siri? I'll show you how!
The following resources were mentioned during the presentation or are useful additional information.
Here’s what was said about this presentation on social media.
@RowdyRabouw from bringing the 🔥 at #odessajs pic.twitter.com/usYKkZFmI0
— A$YNC Mike (@mikemaccana) July 7, 2018
@RowdyRabouw The most original slide deck today on @OdessaJS for sure!
— Eyal Eizenberg (@EyalEizenberg) July 7, 2018
@RowdyRabouw invites you to unleash your web skills on native! Don’t miss his fascinating talk at OdessaJS 2018😉 pic.twitter.com/FDrwKjLVsv
— OdessaJS (@OdessaJS) May 2, 2018