Как мы отбираем хлеб у мобильных разработчиков
Сергей Мелашич
Agilie
Обеспечиваем полный цикл разработки
от дизайна до тестирования
Отделы Front-end, Back-end, iOS и Android разработки
Более 9 лет на рынке,
офисы в Днепре и Запорожье
Немного о тенденциях


Javascript разработчики могли бы захватить
мир, если бы заказчики правки по проектам
не прислали
(золотой фонд цитат Джейсона Стетхема)
Наше преимущество?

JavaScript






Кросс-платформенные решения

Mobile OS functionality
Plugins
Native Libs
WebView JS Bridge
JavaScript
Html, CSS
Browser



Легкий и быстрый старт
Поддержка множества платформ
Доступ к основным встроенным функциям
Работа в автономном режиме
Огромное сообщество
Никаких ограничений по UI
Нет доступа к расширенным встроенным функциям
Сложности обработки данных


Mobile OS functionality
API Meta
Native Libs
JS to Native Bridge
JavaScript
UI Layout & Styles
JavaScript Engine



Повышенная производительность
Работа в UI потоке
Есть ограничения по UI
Общие абстракции над платформами
UI компоненты компилируются в нативные
Метаданные
Возможность прямой работы с нативным кодом
Довольно большое сообщество

Итак, почему NativeScript?

Негативный опыт с Cordova в прошлом
ReactJS не входит в наш технологический стек
Интеграция с Angular "из коробки"
Marketplace и готовые решения
Большое сообщество в Slack



$55,838M
$39,128M
$88M
Разработка компании Telerik
Ваше первое приложение
<ActionBar text="Dashboard"></ActionBar>
<GridLayout rows="160, 80, *">
<app-expenses-chart row="0">
</app-expenses-chart>
<Label [text]="balance.income"
row="1">
</Label>
<Button text="Sign In"
row="2">
</Button>
</GridLayout>
$ tns create MyFirstApp


<DatePicker loaded="onDatePickerLoaded"
day="20"
month="04"
year="1980">
</DatePicker>
Визуальные
компоненты
import {
confirm,
ConfirmOptions
} from "tns-core-modules/ui/dialogs";
const options: ConfirmOptions = {
title: 'Confirm',
message: 'Are you sure?',
okButtonText: 'Ok',
cancelButtonText: 'Cancel',
}
confirm(options).then(handleResponse);
Визуальные
компоненты


Нативные функции
import { Image } from "tns-core-modules/ui/image";
import * as camera from "nativescript-camera";
camera.takePicture()
.then((imageAsset) => {
const image = new Image();
image.src = imageAsset;
})
.catch(handleError);
import * as geolocation from "nativescript-geolocation";
import { Accuracy } from "tns-core-modules/ui/enums";
geolocation.getCurrentLocation({
desiredAccuracy: Accuracy.high,
maximumAge: 5000,
timeout: 10000
})
.then(loc => {
handleLocation(loc)
})
.catch(handleError);
const clipboard = require("nativescript-clipboard");
clipboard.setText(someContent)
.then(() => {
console.log("Success");
});
clipboard.getText()
.then((content) => {
console.log("Clipboard content: " + content);
});

Производительность






Разметка и разметка


StackLayout &
GridLayout


AbsoluteLayout
Позиционировать можно только top и left
Нельзя позиционировать в относительных величинах
Как определять размеры?
Ни onNavigatedTo,
ни ngAfterViewInit не работают
Можно использовать setTimeout, но вы ведь не будете, правда?
Только loaded можно верить
<ScrollView>
<GridLayout rows="auto * auto auto"
class="dark">
<StackLayout row="0">
<Image [src]="wallet.logo"></Image>
<Label [text]="cashAmount(wallet) | currency">
</Label>
...
</StackLayout>
...
<StackLayout row="1">
...
<Button text="Submit"
(loaded)="handleLoaded()">
</Button>
</StackLayout>
</GridLayout>
</ScrollView>
<ul class="dropdown-menu">
<li *ngFor="let option of options">
<a href="#">
<span [class]="option.type">
</span>
{{ option.label }}
</a>
</li>
</ul>
<StackLayout class="dropdown-menu">
<StackLayout
*ngFor="let option of options"
orientation="horizontal">
<Label [class]="option.type"></Label>
<Label [text]="option.label"></Label>
</StackLayout>
</StackLayout>

Dropdown - тудушник
в мире верстки
Layout должен быть максимально "плоским"
<AbsoluteLayout class="dropdown-menu">
<ng-template ngFor let-option
[ngForOf]="options"
let-i="index">
<Label [class]="option.type"
[marginTop]="i * 35">
</Label>
<Label [text]="option.label"
[marginTop]="i * 35">
</Label>
</ng-template>
</AbsoluteLayout>
<StackLayout class="dropdown-menu">
<StackLayout
*ngFor="let option of options"
orientation="horizontal">
<Label [class]="option.type">
</Label>
<Label [text]="option.label">
</Label>
</StackLayout>
</StackLayout>
<DockLayout class="page" stretchLastChild="false">
<StackLayout dock="top" class="card">
<GridLayout columns="*, auto" rows="auto" class="card-h">
<StackLayout col="0" row="0" verticalAlignment="center">
<StackLayout orientation="horizontal">
<Label text="Terminator" class="h3 title m-r-2">
</Label>
<Label text="(1984)" class="h3 caption"></Label>
</StackLayout>
<Label text="Action" class="h5 caption"></Label>
</StackLayout>
<Image col="1" row="0"
src="~/images/logo.png" class="img-circle">
</Image>
</GridLayout>
<StackLayout class="hr-light"></StackLayout>
<StackLayout class="card-body">
<Label text="Lorem ipsum..."
textWrap="true" class="body caption"></Label>
</StackLayout>
</StackLayout>
<Button dock="bottom" text="Button"></Button>
</DockLayout>
<GridLayout columns="auto, *, auto"
rows="auto, auto, auto, auto"
dock="top" class="card">
<Label col="0" row="0"
text="Terminator"
class="h3 title m-r-2 m-l-12 m-t-10">
</Label>
<Label col="1" row="0" text="(1984)"
class="h3 caption m-t-10">
</Label>
<Label col="0" row="1" text="Action"
class="h5 caption m-l-12 p-b-10">
</Label>
<Image col="2" row="0" rowSpan="2"
src="~/images/logo.png"
class="img-circle m-r-12"
verticalAlignment="center">
</Image>
<Label col="0" colSpan="3" row="2"
class="hr-light">
</Label>
<Label col="0" colSpan="3" row="3"
text="Lorem ipsum..." textWrap="true"
class="body caption m-x-12 p-y-10">
</Label>
</GridLayout>
Нет, серьезно - МАКСИМАЛЬНО "плоским"
Используйте атрибуты вместо компонентов
<GridLayout>
<StackLayout>
<app-element></app-element>
</StackLayout>
</GridLayout>
<GridLayout>
<StackLayout class="wrapper-class" appElement>
</StackLayout>
</GridLayout>
@Component({
selector: 'app-element, [appElement]',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppElement {
...
}
Списки - еще одна головная боль

Используйте ngFor только, если все элементы списка умещаются на экране
И все равно не забывайте про trackBy
Для больших списков используйте ListView и RadListView
Не забывайте посматривать на
https://docs.nativescript.org/ui/professional-ui-components
Переиспользование кода






Angular Schematics
$ ng add @nativescript/schematics
SharedProject
Web App
Mobile App

ng serve
npm run ios
npm run android

Переиспользуем сервисы
app.module.ts
app.module.tns.ts
HttpClientModule
NativeScriptHttpClientModule
HttpClient
Web App
Mobile App
export type StorageInterface = {
set: (key: string, val: string) => void;
get: (key: string) => string;
delete: (key: string) => void;
clear: () => void;
};
import {Injectable} from '@angular/core';
import {StorageInterface} from '~/app/interfaces';
@Injectable({
providedIn: 'root'
})
export class StorageService implements StorageInterface {
clear(): void {
sessionStorage.clear();
}
delete(key: string): void {
sessionStorage.removeItem(key);
}
get(key: string): string {
return sessionStorage.getItem(key);
}
set(key: string, val: string): void {
sessionStorage.setItem(key, val);
}
}
import {Injectable} from '@angular/core';
import {StorageInterface} from '~/app/interfaces';
const cache = require('nativescript-cache');
@Injectable({
providedIn: 'root'
})
export class StorageService implements StorageInterface {
clear(): void {
cache.clear();
}
delete(key: string): void {
cache.delete(key);
}
get(key: string): string {
return cache.get(key);
}
set(key: string, val: string): void {
cache.set(key, val);
}
}
Пишем сервисы
storage.service.ts
storage.service.tns.ts
Переиспользуем компоненты
Web App
Mobile App
name.component.html
name.component.tns.html
name.component.scss
name.component.tns.scss
name.component.class.ts
name.component.ts
name.component.tns.ts
Переиспользуем компоненты (Router)
import {InjectionToken} from '@angular/core';
export const UNIVERSAL_ROUTER = new InjectionToken<string>('Custom router');
import {Router}
from '@angular/router';
...
@NgModule({
...
providers: [
...
{
provide: UNIVERSAL_ROUTER,
useExisting: Router
},
]
})
export class AppModule {
...
}
import {RouterExtensions}
from 'nativescript-angular/router';
...
@NgModule({
...
providers: [
...
{
provide: UNIVERSAL_ROUTER,
useClass: RouterExtensions
}
]
})
export class AppModule {
...
}
app.module.ts
app.module.tns.ts
Метаданные
Метаданные - соответствие между JavaScript и
Objective-C / Java кодом
const Toast = android.widget.Toast;
Toast
.makeText(
app.android.context,
message,
Toast.LENGTH_SHORT)
.show();
import android.widget.Toast;
Toast
.makeText(
getApplicationContext(),
message,
Toast.LENGTH_SHORT)
.show();
import {topmost} from 'tns-core-modules/ui/frame';
declare const CSToastManager: any;
CSToastManager.setDefaultDuration(2.0);
CSToastManager.setDefaultPosition('CSToastPositionBottom');
topmost().ios.controller.view.makeToast(message);
[CSToastManager setDefaultDuration:2.0];
[CSToastManager
setDefaultPosition:@"CSToastPositionBottom"];
[self.view makeToast:message];
Java
Objective C
JavaScript
JavaScript
Плагины
plugin.common.ts
plugin.ios.ts
plugin.android.ts
export abstract class Common {
abstract property;
abstract method();
}
export class PluginClass extends Common {
property;
method() {
// iOS specific code
};
}
export class PluginClass extends Common {
property;
method() {
// Android specific code
};
}
Вместо заключения
Мы не используем NativeScript, если в приложении задействованы низкоуровневые или специализированные API
Мы не используем NativeScript, если элементы дизайна слишком далеки от стандартных и сложнокастомизируемы
Использование NativeScript оправдано даже для одной платформы, если у вас есть специалисты по бизнес-логике приложения
Использование NativeScript оправдано, если дизайнеры согласовывают элементы дизайна с разработчиками
NativeScript хорошо зарекомендовал себя, обеспечивая поддержку начиная с Android 5 и iOS 9
Благодарю за внимание
Пожалуйста, разбудите спящих соседей
SergeyMell
Seroga.Mell
Sergey Melashych
sergey.mell@agilie.com
NativeScript
By Sergey Mell
NativeScript
- 459