Flutter vs React Native – porównanie frameworków cross-platform

Technologie wieloplatformowe wpisały się na stałe w środowisko twórców oprogramowania mobilnego. W tej chwili głównymi graczami na rynku są Flutter i  React Native. Przedstawiamy porównanie tych dwóch technologii z punktu widzenia programisty, z nadzieją, że uda nam się pokazać, co można zyskać decydując się na wybór jednej z nich do zbudowania twojej następnej aplikacji mobilnej.

Historia powstania React Native

React Native został stworzony przez Facebooka. Historia rozpoczyna się od wewnętrznego projektu hackatonu latem 2013. Pierwszy raz, publicznie, zaprezentowano RN w 2015 roku na React.js Con. W marcu 2015, na F8, Facebook poinformował, że React Native jest już dostępny na GitHub’ie. Facebook, rozwijając własne produkty, takie jak chociażby świetnie znany Messanger oraz doświadczenie w webowym frameworku React.js stworzył rozwiązanie dla natywnych komponentów aplikacji z wątku JavaScript.

Historia powstania Flutter’a

Pierwsza wersja Fluttera, jeszcze pod nazwą kodową “Sky”, została przedstawiona w 2015 roku na konferencji deweloperów Dart. Wersję 1.0 wypuszczono na evencie Flutter Live, 4.12.2018. Była to pierwsza stabilna wersja, pozwalająca na kompilowanie natywnych aplikacji Android i iOS z jednego kodu. Oprócz tego, jako najważniejsze zalety frameworka podano: płynność działania na poziomie natywnej aplikacji, oraz zachowujący stan aplikacji Hot Reload.

Jak działa React Native?

W React Native podstawowym językiem programowania jest JavaScript. Standardowo dołączone jest rozszerzenie JSX ułatwiające pisanie deklaratywnych komponentów UI, tak jak w React.js. Programista nie jest ograniczony w używaniu innych transpilerów. Stąd często w programowaniu React Native spotyka się Flow, TypeScript, czy ReasonML. Ponadto można używać najnowszych konstrukcji języka jak async/await czy generatory. Docelowo w aplikacji na telefonie znajduje się zawsze JavaScript (po transpilacji).

React Native zawiera zestaw standardowych elementów UI, wyposażonych w bindingi do natywnych komponentów, które będą użyte w aplikacji w zależności od systemu operacyjnego. Komponenty mogą być dowolnie grupowane w hierarchicznej strukturze deklaratywnego widoku. Mogą renderować inne komponenty deklaratywne, lub komponenty z natywnymi bindingami.

Komponent (React.Component) jest fundamentalną koncepcją w React Native. Jest to obiekt (albo funkcja) który posiada “props” i “state”. Dla zadanych props i state komponent ma zwracać jakiś widok, i docelowo tylko gdy props albo state się zmieni. Props są rodzajem argumentów dla komponentu, które ustawiane z są z zewnątrz – komponent nie ma nad nimi kontroli. State natomiast to dane wewnętrzne, prywatne, w całości i na wyłączność pod kontrolą komponentu.

React Native stosuje jednokierunkowy przepływ danych (unidirectional data flow), co odróżnia go od innych deklaratywnych frameworków takich jak Angular (two-way data binding). Skutkuje to lepszą wydajnością i łatwiejszym debugowaniem. Jednokierunkowy przepływ danych w React Native polega na tym że komponent w fazie renderowania ma do dyspozycji read-only props i state. Może je dowolnie przekształcać, przekazywać w dół hierarchii widoku, ale nie powinien ich mutować bezpośrednio, a tym bardziej nie mogą tego zrobić komponenty potomne które otrzymają te dane jako props. Modyfikacja bezpośrednia propsów lub state nie będzie skutkować odświeżeniem widoku. Aby tego dokonać należy posłużyć się metodą setState komponentu (albo podobną funkcją w przypadku użycia Reduxa czy innych rozwiązań). Obsługa zdarzeń przez komponenty potomne powinna zostać zrealizowana przez przekazanie w props callbacków do obsługi eventów, które w implementacji docelowo wykonują setState rodzica według potrzeb.

Czym jest Flutter?

Aplikacje Flutter pisze się w Dart, obiektowym języku z systemem typów, łączącym statyczne typowanie z kontrolą w runtime. Zezwala on na inferencję typów, dzięki czemu można pomijać ich deklaracje. Posiada mechanizm async/await, który wraz z typem Future pozwala na pisanie asynchronicznego kodu tak, jakby był on synchroniczny. Jeżeli znasz takie języki jak Kotlin czy JavaScript, to jego przyswojenie będzie bezproblemowe.

Podstawowym budulcem interfejsu użytkownika jest Widget. Zasada działania jest podobna do React Native – de facto dokumentacja Fluttera wprost wymienia Reacta jako inspirację. Poszczególne elementy układa się w hierarchiczną strukturę, budowaną od nowa za każdym razem, gdy stan aplikacji się zmienia. Nie znaczy to jednak, że widok jest co chwilę budowany od nowa. Zwrócona struktura jest jedynie pośrednikiem, na podstawie którego framework modyfikuje widoczny interfejs. W ten sposób Flutter zamienia znany z platform natywnych styl imperatywny na deklaratywny, zdejmując z programisty ciężar pisania i zarządzania przejściami pomiędzy możliwymi stanami interfejsu.

Flutter zapewnia szeroką paletę gotowych widgetów, zgodnych z wytycznymi Material Design. Programista ma do dyspozycji między innymi przyciski, pola tekstowe z tytułami, czy wrappery zarządzające umiejscowieniem belek czy pływających przycisków. Pewien problem może sprawiać rozmieszczanie elementów na ekranie. Pozycję i dekorację komponentów definiujemy umieszczając je w kontenerach. Często powoduje to rozrastanie się struktury i zmusza do zapamiętania zachowań i zależności pomiędzy kilkoma rodzajami kontenerów.

Architektura BLoC (Bussiness Logic Component) jest jednym z rozwiązań proponowanych przez Google. Umieszcza ona przypadki użycia w osobnych klasach. Komunikacja z nimi odbywa się przez strumienie asynchroniczne, interfejs publikuje zdarzenia w odpowiednich “wejściach” BLoC-ów i jednocześnie nasłuchuje wyjść, którymi “spływają” kolejne stany aplikacji. Pośrednikiem pomiędzy tymi komponentami a interfejsem jest widget StreamBuilder, odbierający dane ze strumieni. Podstawowym założeniem tej architektury jest dostosowanie aplikacji do działania w środowisku asynchronicznym – w którym rozmaite dane napływają w dowolnych momentach i w dowolnej kolejności. W tym rozwiązaniu na pewno odnajdą się programiści natywni, zaznajomieni z RxJavą czy architekturą MVVM. Nie jest to jednak jedyna droga, Flutter może współpracować np. z Reduxem a stan może być propagowany przez same komponenty. Proces ten można też uprościć przez InheritedWidget skracający drogę do docelowego komponentu, jaką muszą przebyć dane. Kolejnym rozwiązaniem może być Provider, biblioteka łącząca Dependency injection i zarządzanie stanem aplikacji. Za jej pomocą programista może dostarczyć niezbędne wartości w odpowiednie miejsca interfejsu. Wstrzykiwać można dowolne obiekty, proste dane, strumienie, bądź obiekty obserwujące zmiany danych (np. ChangeNotifier).
Wszystkie powyższe rozwiązania są dobrze udokumentowane i objaśnione w oficjalnych przewodnikach. Wielu programistom ułatwi to wdrożenie znanych już im rozwiązań.

Techniczne porównanie narzędzi – jakie narzędzia są dostępne dla poszczególnych frameworków?

React Native

Hot / Live reload

Mocne przyśpieszenie procesu developmentu, gdyż każdą zmianę w JavaScript możemy w sekundę zobaczyć na telefonie. Funkcja ograniczona tylko do trybu debugowania (musi być podłączony metro bundler), a zmiany natywne nie są widoczne.

Expo

Wystarczy zainstalować jedną aplikację Expo na telefonie, a kod aplikacji RN możemy pobierać z internetu, czy kodu QR i błyskawicznie aktualizować gdy się zmieni. Nie nadaje się do tworzenia aplikacji produkcyjnych, a raczej do szybkiego prototypowania. Jeśli tylko chcemy dodać prawdziwe pushe, czy Crashlytics albo jakąkolwiek bibliotekę nie ujętą w Expo SDK, musimy zakończyć pracę z Expo i wyeksportować (expo eject) projekt do wersji “pełnej”.

snack.expo.io 

Nie musimy nawet instalować Expo w telefonie, możemy symulować aplikację w przeglądarce!

React Native CLI 

Program, który potrafi nam wygenerować nowy projekt React native. Niestety bardzo ograniczone opcje, a programu używa się z terminala.
Z doświadczenia mogę powiedzieć że po utworzeniu projektu nadal jest mnóstwo pracy inicjalizacyjnej jak generowanie certyfikatów, dodawanie wariantów dev/prod czy podpinanie Crashlytics. Można przygotować szablon projektu, by inicjalizacja nowych projektów przebiegała szybciej, ale środowisko React Native tak szybko się zmienia, że taki szablon wykorzystalibyśmy tylko raz. Słabiutko w porównaniu do kreatorów projektów w Android Studio / XCode.

Testowanie 

Przede wszystkim niewielkie mam doświadczenie w tej kwestii.
Jest kilka frameworków pod React Native. Domyślnie włączony jest “Jest”. Łatwo możemy dodać nowe testy jednostkowe. Wrzucamy do __tests__ i odpalamy poleceniem “npm test”. Trzeba uważać z importami do natywnych modułów – potrzebne są mocki, a tych często brakuje w paczkach z npm, albo działają źle.

Wdrażanie i budowanie apki


Wszystko trzeba robić natywnie i jesteśmy ograniczeni do tego co oferuje Android Studio / XCode. Szczególnie problematyczne dla programistów niedoświadczonych wystarczająco z developmentem natywnym (ja) – nie dość że 2x więcej platform do zarządzania, to jeszcze na każdej platformie idzie 2x wolniej niż natywnemu deweloperowi.Brak zalet React Native na tym obszarze.

FLUTTER 

Flutter oferuje pluginy ułatwiające pracę w Android Studio, oraz Visual Studio Code. Wspomagają one tworzenie projektów, debugowanie oraz funkcję Hot Reload. 

DevTools 

Narzędzie do debugowania i profilowania apki, odpalane w przeglądarce. Pozwala ono między innymi na przeglądanie interfejsu w formie drzewa i na sprawdzanie poszczególnych widgetów. Umożliwia też śledzenie zużycia pamięci, zasobów CPU i testowanie apki pod kątem wydajności.

Debugger OEM (Original Equipment Manufacturer) 

Pozwala na debugowanie nie tylko kodu Dart, ale też plików wygenerowanych dla konkretnej platformy. Jest to szczególnie użyteczne przy pisaniu własnych rozwiązań dla danego systemu.

Testowanie 

Flutter oferuje własny plugin przeznaczony do przeprowadzania testów jednostkowych, integracyjnych, oraz sprawdzania widgetów. Do mockowania zależności, oficjalna dokumentacja zaleca Mockito. 

Flutter wspiera flavory pozwalające konfigurować różne warianty budowania, wyszukując istniejące android productFlavors oraz iOS schema. Można odwoływać się do nich bezpośrednio w kodzie Dart a także wywoływać je bezpośrednio z Flutter CLI, jednak najpierw muszą być skonfigurowane osobno dla każdej platformy. Proces opisują jedynie artykuły od community.

Do przygotowania wersji release konieczne jest samodzielne skonfigurowanie każdej platformy osobno. Dokumentacja opisuje proces edycji poszczególnych plików, jednak znajomość Androida oraz iOS na pewno przyspieszy ten proces.

Flutter – Learning curve

Google aktywnie zachęca deweloperów do nauki i przesiadki na Fluttera. Dostępnych jest wiele materiałów szkoleniowych takich jak prelekcje, czy seria wideo tutoriali. 

Dokumentacja jest wyjątkowo wyczerpująca, zapewnia wiele przykładów i objaśnia wiele aspektów pracy z frameworkiem, od budowania interfejsu po pisanie testów. Autorzy frameworka przedstawiają gotowe przepisy na wiele elementów współczesnej aplikacji. Proponują też nowoczesny wzorzec architektoniczny, BLoC.  Ogromną zaletą jest sekcja przeznaczona dla programistów znających inne popularne platformy, mobilne oraz web. Tłumaczy ona kluczowe różnice pomiędzy nimi a Flutterem. 

Z tego powodu nauka frameworka nie sprawia dużych trudności. W zależności od posiadanego doświadczenia, konieczna może być nauka takich zagadnień jak np. ReactiveX, deklaratywny UI czy wzorzec async/await. Framework wykorzystuje język Dart, nie tak popularny jak np. JavaScript czy Java, więc część programistów będzie musiała poświęcić czas na przyswojenie składni. W rankingu popularności Stack Overflow z 2019, zajął 22 miejsce z wynikiem 1.9% – wynik kontrastuje z zajmującym pierwsze miejsce JavaScript. Jest on jednak dość podobny do innych języków obiektowych i posiada bezpieczeństwo typów, więc przesiadka nie powinna sprawiać problemów. 

Ponadto, we wspomnianym rankingu, w kategorii “Most Loved”, oba języki mają niemal identyczny wynik, JS – 66.8%, Dart – 66.3%. Kategoria pokazuje ilu deweloperów jest zainteresowanych dalszą pracą z daną technologią. Biorąc pod uwagę, że większość wyników jest zbliżona do 60%, Dart może być technologią łatwą i ciekawą w nauce.

Podsumowanie

Frameworki wieloplatformowe rozwijają się i zdobywają coraz większą popularność. Rozwiązania produkujące aplikacje zbliżone do natywnych, zdecydowanie wypierają te hybrydowe, oferując lepszą wydajność oraz natywny wygląd i zachowanie aplikacji. Flutter i React Native zdają się być najważniejszymi konkurentami w dziedzinie cross-platform.

Oba frameworki budują widoki w sposób deklaratywny. Wprowadzają analogiczne pojęcia Component i Widget, będących podstawowym budulcem aplikacji, działającymi na podstawie niezmiennego stanu. Każdy z nich oferuje też funkcję Hot Reload pozwalającą na szybkie przeładowanie działającej aplikacji i sprawdzanie wprowadzonych zmian.

U swoich podstaw, obie technologie są dość podobne. Posługują się deklaratywnym stylem budowania UI i są dość elastyczne, pozwalając programiście na zastosowanie różnych technik zarządzania stanem aplikacji. Dostęp do funkcji urządzeń zapewniają pluginy. 

React Native na tym polu bardziej polega na społeczności, mając do dyspozycji kilka osobnych bibliotek dla tych samych zadań. Może być to zarówno wada, jak i zaleta. Flutter pozostaje pod większą kontrolą swoich autorów, którzy sami zapewniają więcej gotowych rozwiązań. Jednocześnie współpracują oni z rosnącym community, które dostarcza brakujących narzędzi, jak np. obsługa bazy danych. 

Ostateczna decyzja o zastosowaniu technologii należy do deweloperów. Flutter jako młodszy framework, siłą rzeczy jest mniej popularny. React Native, posługuje się JavaScript i zbudowany jest na bazie React.js, może być więc bardziej atrakcyjny dla web developerów. 

Niemniej obie technologie posiadają pewien próg wejścia. Programiści doświadczeni w technologiach natywnych będą musieli przyswoić wzorce znane raczej z web. Natomiast front-endowcy będą musieli zmierzyć się ze specyfiką platform mobilnych. Przydatna będzie także wiedza na temat platform docelowych, choćby dla usprawnienia procesu budowania i wdrażania aplikacji.

Rate this post