Autor: Andreas Moeller i Kristian Kissling
Dwa otwarte frameworki, React Native [1] i NativeScript [2], pomagają programistom tworzyć natywne aplikacje dla Androida i IOS-a za pomocą JavaScriptu. Podejście to ma szereg zalet w porównaniu z klasycznym podejściem do tworzenia aplikacji w JavaScripcie, np. za pomocą Meteora [3], który wykorzystuje Apache Cordova [4] i WebView.
Słowem wstępu
Natywne aplikacje dla Androida tworzy się zazwyczaj w Javie lub Kotlinie, natomiast te na iOS-a – w Objective-C i Swifcie [5]. Dodatkowo programiści Androida mogą korzystać z natywnego kodu C/C++ za pośrednictwem Java Native Interface (JNI) [6]. Środowiska programistyczne Android Studio [7] i Xcode [8] pomagają tworzyć aplikacje i wprowadzić je do sklepów – Apple AppStore i Google Play.
Takie podejście ma jednak swoje wady. Instalacja Android Studio jest złożona i długo trwa, a Xcode działa wyłącznie na macOS-ie. Jeśli chcemy przenieść aplikację androidową na iOS-a, musimy przepisać kod na Objective-C lub Swift, co nie tylko wymaga dodatkowej pracy, ale również wprowadza dodatkowe źródła potencjalnych błędów przez redundancję kodu.
Rysunek 1: Cordova: bez Javy i Android Studio twórca aplikacji androidowych niczego nie stworzy w Ubuntu.
Rysunek 2: Most łączy świat JavaScriptu z kodem natywnym.
Listing 1: Instalacja Expo na Ubuntu 17.10
01 curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
02 sudo apt-get install nodejs
03 sudo npm install exp --global
04 exp init react-test
05 cd react-test
06 exp start
Z kolei klasyczne aplikacje webowe wykorzystują HTML, CSS i JavaScript, działając w przeglądarkach na różnych systemach. Aplikacje te mają jednak ograniczony dostęp do systemu: uruchamiane są w piaskownicy przeglądarki i korzystają tylko z niektórych API, co ogranicza ich możliwości.
W 2009 r. udostępniono projekt PhoneGap (obecnie Apache Cordova), który umożliwia lepsze połączenie aplikacji webowych z systemem. Dzięki Cordovie aplikacje działają w WebView – jak w przeglądarce. Dzięki różnym wtyczkom możemy dodatkowo zwiększyć integrację z systemem działających w ten sposób aplikacji.
Rysunek 3: Expo ułatwia programistom życie, udostępniając kod QR do instalacji aplikacji.
Rysunek 4: The app uses theUIAlertControllerclass on iOS.
Meteor
Framework javascriptowy Meteor [3] ewoluował stopniowo z frameworka służącego do tworzenia aplikacji webowych działających w czasie rzeczywistym [9] do otwartoźródłowej platformy, którą twórcy reklamują jako najlepszy sposób na uruchamianie aplikacji. I faktycznie – instalacja i utworzenie podstawowej aplikacji w powłoce w Ubuntu 17.10 sprowadza się do wydania polecenia:
curl https://install.meteor.com/ | sh meteor create example
Jednak próba zbudowania wersji dla Androida poleceniem meteor add-platform android kończy się niepowodzeniem (Rysunek 1), ponieważ pod maską Meteor bazuje na Cordovie, która wymaga Javy i Android Studio. Co więcej, w Ubuntu nie możemy zbudować wersji na iOS-a.
Tworząc aplikację, możemy przynajmniej wybrać między własnym frameworkiem javascriptowym Meteora, czyli Blaze, a AngularJS-em [10] i Reactem [11]. Meteor nie pomaga jednak zbytnio programiście w dystrybucji aplikacji. Choć polecenie meteor build tworzy odpowiednie wersje pakietów na Androida i iOS-a, to jednak umieszczenie aplikacji w sklepach wymaga ręcznej pracy.
React Native
Natywne frameworki stosują nieco inne podejście: za pomocą JavaScriptu tworzymy aplikacje działające natywnie na Androidzie i iOS-ie, dzięki czemu tworzymy most między aplikacjami webowymi a tymi działającymi na określonych platformach.
Podczas działania aplikacji React Native [1] pozostawia ją silnikowi javascriptowemu. Kod działa we własnym wątku i uzyskuje dostęp do natywnego kodu i API za pomocą mostu [12] (Rysunek 2). Graficzny interfejs użytkownika nie jest tworzony jak w Meteorze za pomocą drzewa DOM, lecz za pomocą odwołań do natywnych kontrolek w Javie lub Objective-C.
Kiedy użytkownik wyzwala jakieś zdarzenie, używając zrenderowanych elementów, React Native przenosi je z powrotem przez most i konwertuje do postaci zdarzenia javascriptowego. React Native próbuje zapobiec stratom wydajności spowodowanym synchronizacją między dwoma światami za pomocą wirtualnego DOM-a, dla którego model udostępnia javascriptowy framework React. W testach praktycznych przykładowe aplikacje React Native działały mniej więcej tak samo wolno jak natywne aplikacje w starszej wersji iOS-a.
Jeśli chcemy projektować aplikacje React Native, może nam w tym pomóc projekt Expo [13] zawierający szereg narzędzi, m.in. środowisko programistyczne XDE (Expo Development Environment), które można również uruchomić w przeglądarce pod nazwą Snack [14]. Można nim także zarządzać z wiersza poleceń po przeprowadzeniu procedury instalacyjnej z Listingu 1.
Listing 2: Przykładowa aplikacja React Native
01 import React from 'react';
02 import { StyleSheet, Button, View, NetInfo } from 'react-native';
03
04 export default class App extends React.Component {
05 render() {
06 return (
07
08
09
10 );
11 }
12 getConnectionInfo() {
13 NetInfo.getConnectionInfo().then(info => alert(info.type+' ('+info.effectiveType+')'));
14 }
15 }
16
17 const styles = StyleSheet.create({
18 container: {
19 flex: 1,
20 backgroundColor: '#ddd,
21 alignItems: 'center',
22 justifyContent: 'center',
23 },
24 });
W wierszach 1 i 2 instalujemy bieżącą wersję Node.js v8 za pomocą debianowego menedżera pakietów Apt. Następnie menedżer pakietów Node, npm, pobiera Expo (exp), łącznie z React Native, i w wierszu 3 instaluje ho w systemie. Z kolei w wierszu 4 konfigurowana jest szablonowa aplikacja react-test. W wierszach 5 i 6 aplikacja jest pakowana, by można ją było uruchomić na smartfonie. Na Rysunku 3 widzimy powłokę po wykonaniu tych poleceń oraz kod QR służący do instalacji.
Aby przenieść spakowaną aplikację na telefon za pomocy kodu QR, użytkownik musi na nim najpierw zainstalować klienta Expo z Google Play lub Apple App Store, a następnie uruchomić go, wskanować kod QR i załadować szablonową aplikację (Rysunek 4).
Jeśli wymagania dotyczące aplikacji wykraczają poza ograniczenia React Native i Expo, możemy podpiąć pod nią kod natywny, który będzie kontrolowany przez JavaScript w aplikacji. W takim scenariuszu musimy jednak zainstalować Android Studio i Xcode.
Przykładowa aplikacja
Na Listingu 2 widzimy przykładowy sposób użycia React Native. Kod ten zachowuje plik App.jsw katalogu react-test. Podobnie jak inne aplikacje React Native, przedstawiony na Listing 2 program bazuje na frameworku javascriptowym React [11], który importujemy w wierszu 1. W kolejnym wierszu pobieramy z React Native komponenty StyleSheet, Button, View oraz NetInfo.
React Native tworzy również jako komponent klasę bazową App (wiersze 4–15) as a component. Jej metoda render() (wiersze 5–11) używa przypominający XML kod JSX, by stworzyć interfejs użytkownika odnoszący się do danego obszaru (wiersze 7–9). W wierszu 8 komponent View implementuje przycisk button [15]. W czasie działania programu Android wyświetli go za pośrednictwem klasy android.widget.Button [16], natomiast iOS – UIButton [17].
Wiersze 12–14 przechowują funkcję zwrotną getConnectionInfo(), na którą wskazuje wartość atrybutu onPress (wiersz 8). Kiedy przycisk zostanie naciśnięty, w wierszu 13 metoda przycisku określa status połączenia urządzenia mobilnego. Funkcja lambda odpowiada na asynchroniczny wynik, wywołując metodę then(), która powinna wyświetlić pozyskany typ połączenia w oknie dialogowym. Jak widzimy na Rysunku 4, podczas testów nie udało się określić statusu połączenia.
W wierszu 7 odniesienie do {styles.container} z wartości atrybutu style przypisuje informacje dotyczące stylu ze statycznego obiektu javascriptowego styles do komponentu View. W ten sposób narysowane zostanie szare tło, a przycisk będzie umieszczony w centrum (wiersze 17–24). Oprócz NetInfo React Native oferuje wiele innych obiektów API związanych z systemem, które zwiększają możliwości SDK.
Rysunek 5: Expo udostępnia pliki dziennika ze zdarzeniami zachodzącymi podczas procesu budowania.
Listing 3: Przykładowa aplikacja w NativeScripcie
01 import { Component } from "@angular/core";
02 import * as connectivity from "connectivity";
03
04 @Component({
05 selector: "my-app",
06 template: ``
07 })
08 export class AppComponent {
09 getConnectionInfo() {
10 switch(connectivity.getConnectionType()) {
11 case 0:
12 alert('none');
13 break;
14 case 1:
15 alert('WiFi');
16 break;
17 case 2:
18 alert('Mobile');
19 break;
20 }
21 }
22 }
Polecenie exp start w ostatnim wierszu Listingu 1 działa jak proces obserwujący. Jeśli zarejestruje zmianę w katalogu projektu, poinformuje o tym serwer pakujący, który również został uruchomiony. Następnie serwer zadba o to, by zaktualizowana wersja aplikacji została dostarczona do podłączonych klientów Expo.
Tabela 1: Porównanie frameworków
Framework
|
IDE
|
Budowanie w chmurze
|
Natywne UI
|
Natywne Moduły
|
Natywne Pakiety
|
Budowanie dla App Store
|
Meteor 1.6.1
|
Nie
|
Nie
|
Nie
|
Tak
|
Tak
|
Nie
|
React Native 52.0
|
XDE 2.22.1
|
Tak
|
Tak
|
Tak
|
Tak
|
Nie
|
NativeScript 3.4.3
|
Sidekick 1.5.1
|
Commercial
|
Tak
|
Tak
|
Tak
|
Nie
|
Rysunek 6: Sidekick po zbudowaniu aplikacji na Androida. Zbudowanie wersji dla iOS-a jest płatne.
Jak to pokazuje przykładowa aplikacja react-test [18], programista może w dowolnej chwili opublikować aplikację za darmo, wykorzystując w tym celu swoje konto Expo; służy do tego przycisk Publish w XDE. Jeśli do pliku konfiguracyjnego app.json w katalogu projektu dodamy też buildidentifier i package, wtedy exp build:android utworzy aplikację androidową w chmurze dostawcy.
Za pomocą URL-a na koncie Expo możemy śledzić proces budowania, korzystając z wiersza poleceń. Na Rysunku 5 widzimy wpisy w plik u dziennika po udanym zbudowaniu pakietu na Androida. Pod maską Expo korzysta z instancji Android Studio lub Xcode. Podobnie jednak jak to jest w przypadku Meteora, dostawca nie zintegrował całkowicie ostatnich kilku etapów niezbędnych do załadowania aplikacji do sklepu – musimy więc ręcznie wykonać kilka czynności.
NativeScript
Framework javascripty NativeScript [2] również pozwala generować aplikacje natywne dla Androida i iOS-a. Podobnie jak zachodzi to w przypadku React Native, wykonanie kodu pozostawione jest odpowiedniemu silnikowi javascriptowemu; wykorzystywany jest tu Android v8 oraz iOS JavaScriptCore (JSC).
NativeScript umożliwia również sprawowanie kontroli nad kodem natywnym za pomocą mostu [19]. Środowisko uruchomieniowe dynamicznie przekazuje obiektom natywnym wywołania metod getter/setter obiektu javascriptowego.
Konfiguracja środowiska programistycznego dla NativeScriptu w Linuksie jest szczególnie podatna na problemy: budowanie aplikacji na iOS-a zawodzi z niewiadomych powodów. Jeśli nie ograniczają nas koszty (patrz Tabela 1), możemy tworzyć aplikacje w chmurze dostawcy podobnie jak w Expo. Pierwszych 100 procesów budowania jest darmowych.
W Ubuntu framework instalujemy poleceniem:
sudo npm install -g nativescript
Generowana jest wtedy przykładowa aplikacja szkieletowa:
tns create ns-test --template nativescript-template-ng-tutorial
Pobieramy środowisko programistyczne Sidekick [20], po czym instalujemy debianowym poleceniem dpkg:
sudo dpkg -i NativeScriptSidekick-amd64.deb
Następnie uruchamiamy Sidekicka, pisząc:
/opt/Native\ Script\ Sidekick/Native\ Script\Sidekick
W przeciwieństwie do React Native, NativeScript obsługuje różne frameworki (AngularJS [10] i Vue.js [21]) i języki (czysty JavaScript lub TypeScript [22]).
Na Listingu 3 wracamy do przykładowej aplikacji z Rysunku 4 i Listingu 2, tym razem korzystając z AngularJS i NativeScriptu. Kod powinien znaleźć się w pliku app/app.component.tsw katalogu ns-test wspomnianej wyżej aplikacji testowej. Pierwszy wiersz Listingu 3 importuje klasę Component z AngularJS, natomiast w wierszu 2 – obiekt connectivity z NativeScriptu. Dekorator w wierszach 4–7 konwertuje definicję klasy AppComponent (wiersze 8–22) do pochodnej klasy Component.
W wierszu 6 tworzymy obszar interfejsu użytkownika za pomocą komponentu Button, by wygenerować przycisk, który po naciśnięciu wywołuje funkcję zwrotną getConnectionInfo(). Kod NativeScriptu synchronicznie określa typ połączenia, a funkcja alert() przesyła wynik użytkownikowi.
Na Rysunku 6 widzimy pomyślne zakończenie budowania aplikacji dla Androida. Nie przetestowaliśmy procesu budowania na iOS-a, ponieważ wymagany certyfikat programisty iOS-a – w przeciwieństwie do dokumentacji – jest obecnie płatny.
Wnioski
Aplikacje Meteora łatwo skonfigurować i skalować dzięki usłudze chmurowej Galaxy. Problemy zaczynają się dopiero przy tworzeniu aplikacji mobilnych.
React Native i NativeScript oferują równorzędne rozwiązania. Radzą sobie bez WebView i wykorzystują natywny kod na iOS-ie i Androidzie. Zarówno natywne API, jak i komponenty interfejsu użytkownika korzystają z mostu. Z punktu widzenia programistów aplikacji przypomina to pracę z przeglądarką i DOM-em, natomiast użytkownikowi łatwiej się pracuje z natywnym interfejsem.
Projekt Expo uzupełnia React Native. Dzięki klientowi Expo, webowemu IDE Snack oraz darmowej przestrzeni w chmurze Expo oferuje idealne środowisko dla nowych projektów i ich społeczności. Dzięki budowaniu aplikacji w chmurze twórcy aplikacji nie muszą nawet instalować i konfigurować Android Studio ani Xcode.
Info
[1] React Native: https://facebook.github.io/react-native/
[2] NativeScript: https://www.nativescript.org
[3] Meteor: https://www.meteor.com
[4] Apache Cordova: https://cordova.apache.org
[5] Swift: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/index.html
[6] Java Native Interface: https://developer.android.com/training/articles/perf-jni.html
[7] Android Studio: https://developer.android.com/studio/index.html
[8] Xcode: https://developer.apple.com/xcode/
[9] Andreas Moeller, Tworzenie aplikacji webowych za pomocą meteora, „Linux Magazine” 5/2014: https://linux-magazine.pl/archiwum/wydanie/126
[10] AngularJS: https://angularjs.org
[11] React: https://reactjs.org
[12] Most dla React Native: http://www.discoversdk.com/blog/how-react-native-works
[13] Expo: https://expo.io
[14] Snack: https://snack.expo.io
[15] Przycisk React Native: https://facebook.github.io/react-native/docs/button.html
[16] Przycisk androidowy: https://developer.android.com/reference/android/widget/Button.html
[17] Przycisk iOS-a: https://developer.apple.com/documentation/uikit/uibutton
[18] Przykładowa aplikacja react-test: https://expo.io/@pam/react-test
[19] Most NativeScript: https://developer.telerik.com/featured/nativescript-works/
[20] Sidekick: https://www.nativescript.org/nativescript-sidekick
[21] Vue.js: https://vuejs.org
[22] Tim Schuermann, Alternatywy dla JavaScriptu, „Linux Magazine” 10/2017: https://linux-magazine.pl/archiwum/wydanie/54