Давайте знакомиться! Меня зовут Игорь. Я разработчик кроссплатформенных мобильных приложений в компании Smartex. В этой статье я расскажу о NativeScript, основных преимуществах платформы и ее недостатках.
Материал будет полезен не только разработчикам, которые уже пишут приложения на React Native, Ionic, Framework7, а также тем, кто собирается погрузиться в разработку мобильных приложений на JavaScript.
Погнали!
Свой путь в кроссплатформенную разработку я начал за пределами Smartex, с Cordova + Framework7.
Framework7 как и Ionic просто UI-библиотека для создания интерфейса которая в последующим просто отрисовывается через WebView. Само приложение (общение с API платформы) создается на платформе Cordova или Capacitor, как раз они и создают экран с WebView и транслирует туда HTML.
Framework7 прекрасно работает с Vue, но по ряду причин он начал меня подбешивать. Мне хотелось найти инструмент именно для нативной разработки, а не на технологии основанной на WebView. Я стал смотреть в сторону Angular и на то, какие вообще есть на рынке технологии для разработки нативных кроссплатформенных приложений с Angular под капотом. Так я и познакомился с NativeScript.
Ниже представлены экраны приложения для работы с рисками Riskhunter сделано нами на платформе NativeScript, запущено в 2022 году и успешно работает на 18 фабриках.
Изображения экранов
Что такое NativeScript
NativeScript (NS) — это платформа для создания нативных кроссплатформенных мобильных приложений на JavaScript. NativeScript позволяет прямиком из JavaScript обращаться к API iOS/Android, минуя слой сторонних плагинов. Пожалуй, это самое главное отличительное преимущество перед его старшим братом React Native. NativeScript не позволяет разрабатывать десктопные версии приложений, как это могут делать Flutter и React Native, но работа над этим ведется. Возможно, она появится в ближайшем будущем. Также в отличие от React Native, который работает только на React, NativeScript позволяет использовать Angular, Vue, React, Svelte или ванильный JS/TS.
Кроссплатформенная разработка
Лагерь людей, занимающихся кроссплатформенной разработкой, делится на две части: тех, кто хочет сэкономить ресурсы, и тех, у кого ресурсов просто нет.
Первая причина выбрать кроссплатформенную разработку — получение двух результатов по цене не вдвое выше, чем один результат. Разработчик на Flutter создает приложение одновременно для iOS и Android, и даже для веба (правда пока только для нетребовательных к виду веб-приложения заказчиков). Один разработчик на C# может создать сразу два приложения, используя Xamarin — это делает разработку приложения дешевле и быстрее. Результат виден одновременно на всех платформах, приложение выглядит более-менее одинаково, и если это соответствует целям проекта — прекрасно.
Вторая причина — в команде уже есть разработчик на JavaScript/TypeScript. Работающий с React/Vue.js/Angular разработчик может создать неплохо выглядящее приложение, иногда даже повторно используя куски логики из уже существующего веб-приложения.
В моем случае роль сыграли оба фактора. На старте проекта в команде были фронтендеры и не было реалистичных сроков и бюджета.
Так как наша команда фронтендеров работает с Vue.js, вариантов было немного: основанные на HTML Ionic, Framework7, и основанный на нативных элементах NativeScript. Сразу скажу, что моё сердце по-прежнему принадлежит Angular, который полностью поддерживается в NativeScript.
От Ionic и Framework мы отказались из-за не-нативного поведения UI и ряда ограничений, хотя на таких платформах можно гораздо быстрее разрабатывать прототипы и доставлять новые фичи.
Мы выбрали NativeScript с Vue под капотом. Спустя пару лет разработки в такой комбинации у нас сложились смешанные ощущения, о которых я хочу поговорить.
Чтобы сразу убедить читателей в преимуществах NativeScript, начну с его минусов:
Очень маленькое сообщество. Активных пользователей сервера NativeScript в Discord можно пересчитать на пальцах. Рыночная доля NativeScript с 2019 по 2022 год упала с 11% до 3%.
Скудный набор плагинов по сравнению с React Native, а многие существующие уже давно не поддерживаются;
Из-за маленькой пользовательской базы даже небольшие поломки приводят к тому, что приходится долго траблшутить код или даже свою среду исполнения. Нередки случаи, когда проект просто перестает собираться с эзотерической ошибкой, без изменений в коде и без обновления среды — “сам по себе”. Собственные инструменты NativeScript не всегда прямо показывают где в подлежащем процессе произошла ошибка, и иногда просто прячут ее.
Ну вот, пожалуй, и всё!
А какие плюсы и киллер-фичи мы имеем?
К основным киллер-фичам я бы отнес три пункта:
NativeScript работает с основными JS-фреймворками (Angular, Vue, React, Svelte) и вовсе без фреймворка;
Прямой доступ к API iOS и Android из JavaScript/TypeScript без бойлерплейта — просто обращайся и пользуйся;
Чрезвычайно энергичная, общительная и активная ядерная команда. У них есть свой Discord, где они всегда готовы обсудить любой вопрос или запрос, и многие запрошенные мною вещи становились реальными фичами фреймворка — быстро и с обратной связью от команды.
Давайте чуть подробнее
Поддержка JS-фреймворков
NativeScript поддерживает основные фреймворки (Angular, Vue, React, Svelte), чистый (Plain) JS или TS (для тех, кто не признает фреймворки вовсе).
Angular и Plain поддерживались с рождения NativeScript, поэтому именно по ним самое большое количество примеров и вопрос на Stack Overflow. После появилась поддержка Vue, React и Svelte. По моему мнению, приложения на NativeScript лучше всего делать с Angular или чистым TypeScript.
Разработка мобильного приложения — это несколько больше, чем просто гонять данные с сервера на экран пользователя, как в вебе. Тут тебе и хранилища нескольких видов, работа с файлами, работа с аппаратной частью телефона, работа с сетью.
Использование Vue несколько усложняет разработку, потому что добавляется еще прослойка Vuex, и свои сервисы мы «дергаем»уже из Vuex.
Также с Vue приходится самому изобретать архитектуру проекта, и эта архитектура может кардинально отличаться от проекта к проекту.
Angular же предлагает готовую хорошо продуманную структуру проекта, свой DI, полную поддержку TypeScript а так же RxJs, которого хватает за глаза при разработке мобильного приложения. С ванильным TypeScript можно попытаться организовать что-то похожее.
Связь с core-командой: история из жизни
В одном личном проекте мне понадобился свайп для элемента списка, как в списке чатов Telegram или WhatsApp. Платформа предлагала один компонент для списков с поддержкой свайпов (https://v7.docs.nativescript.org/ui/components/radlistview/overview), но анимация была не та, которую хотелось бы видеть в проекте.
Мне пришлось сделать свой компонент, который я после показал основному разработчику NativeScript Nathan Walker.
В процессе диалога мы сделали вывод что, действительно нет альтернативы компоненту RadListView (ссылка выше), и что надо что-то делать. Спустя пару месяцев Натан опубликовал статью о том, как реализовать свайпы в CollectionView для iOS, а после и для Android.
В Discord парни из команды NativeScript охотно отвечают на вопросы. А еще каждый месяц 4-го числа в Discord проходит “Office Hours”. Команда рассказывает о процессе разработки NativeScript и отвечает на вопросы, которые можно задать по ходу стрима.
Доступ к API платформы
NativeScript позволяет обращаться к платформе прямиком из JS и это очень клево, так как дает возможность работать с платформой, не зная Java, Kotlin, Swift и тем более Objective-C.
Ниже разберем простой код и как к нему может прийти js-разработчик. Пример взят с официального сайта.
Допустим, появилась задача узнать уровень батареи. Мы идем в гугл и находим документацию https://developer.apple.com/documentation/uikit/uidevice.
Тут мы видим класс UIDevice и что он позволяет получить информацию о конкретном девайсе.
Далее мы переходим в наш редактор VS Code в класс компонента и начинаем печатать UIDevice. Редактор нам подсказывает,какие методы содержатся в классе UIDevice.
Мы видим все задекларированные классы, методы, свойства для конкретной платформы.
Ниже представлен полностью рабочий код для доступа к уровню батареи устройства iOS/Android не написав ни строчки на Swift или Java.
// iOS
const formatMessage = level => `The Battery Level is: ${level}%`;
const value = UIDevice.currentDevice.batteryLevel * 100;
alert(formatMessage(value));
// Android
const formatMessage = level => `The Battery Level is: ${level}%`;
const value = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
alert(formatMessage(value));
А теперь давайте рассмотрим задачку посложней. Мы напишем мини-плагин для работы с SDK сервиса OneSignal для Android и попробуем обратиться к его методам.
Создаем в корне директорию plugins/onesignal со следующим содержимым:
Нас тут интересует plugins/onesignal/platfors/android/include.gradle и plugins/onesignal/index.android.ts - именно он будет работать на Android.
// include.gradle
dependencies {
implementation 'com.onesignal:OneSignal:[5.0.0, 5.99.99]'
}
// index.android.ts
import { Utils } from '@nativescript/core';
declare const com: any;
export class OneSignal {
// основная магиях происходит в этой строке
private _oneSignal = com.onesignal.OneSignal;
static init(): void {
this._oneSignal.initWithContext(Utils.android.getApplicationContext(),
'opensignal_app_id');
}
}
Теперь мы можем работать с классом OneSignal пакета com.onesignal из JavaScript. Что мы и делаем в методе init().