React Native dla Androida: jak stworzyliśmy pierwszą wieloplatformową aplikację React Native-Facebook Engineering

na początku tego roku wprowadziliśmy React Native dla iOS. React Native przenosi to, do czego programiści są przyzwyczajeni od Reacta w sieci-deklaratywne, samodzielne komponenty interfejsu użytkownika i szybkie cykle programowania — na platformę mobilną, zachowując szybkość, wierność i odczucia natywnych aplikacji. Dziś z przyjemnością udostępniamy React Native dla Androida.

na Facebooku używamy Reacta natywnego w produkcji już od ponad roku. Prawie dokładnie rok temu nasz zespół rozpoczął prace nad aplikacją Ads Manager. Naszym celem było stworzenie nowej aplikacji, która pozwoli milionom osób reklamujących się na Facebooku zarządzać swoimi kontami i tworzyć nowe reklamy w podróży. Okazało się, że jest to nie tylko pierwsza w pełni natywna aplikacja na Facebooku, ale także pierwsza wieloplatformowa. W tym poście chcielibyśmy podzielić się z Wami tym, jak stworzyliśmy tę aplikację, jak React Native umożliwił nam szybsze poruszanie się i jakie wnioski wyciągnęliśmy.

wybierając React Native

jeszcze niedawno React Native był nową technologią, która nie została sprawdzona w produkcji. Chociaż opracowanie nowej aplikacji opartej na tej technologii wiązało się z pewnym ryzykiem, potencjalne korzyści przeważyły nad nim.

Po pierwsze, nasz początkowy zespół składający się z trzech inżynierów produktu był już zaznajomiony z Reactem. Po drugie, aplikacja musiała zawierać wiele skomplikowanej logiki biznesowej, aby dokładnie obsłużyć różnice w formatach reklam, strefach czasowych, formatach daty, walutach, konwencjach walutowych i tak dalej. Wiele z tego zostało już napisane w JavaScript. Perspektywa napisania całego tego kodu w Objective-C tylko po to, aby później napisać go w Javie dla wersji aplikacji na Androida nie była atrakcyjna — ani nie byłaby wydajna. Po trzecie, w React Native łatwo byłoby zaimplementować większość powierzchni interfejsu, które chcieliśmy zbudować — wyświetlając wiele danych w postaci list, tabel lub Wykresów. Inżynierowie produktu mogli natychmiast produktywnie wdrażać te poglądy, o ile wiedzieli, że reagują.

oczywiście niektóre funkcje stanowiły wyzwanie dla tej nowej platformy — na przykład edytor obrazów, który pozwala reklamodawcom powiększać i kadrować zdjęcie, oraz widok mapy, który pozwala reklamodawcom kierować reklamy do osób w określonym promieniu lokalizacji. Innym przykładem jest nawigacja panele nawigacyjne, która pomaga reklamodawcom wizualizować hierarchię reklam na ich kontach. Dało nam to możliwość dalszego rozwijania platformy.

najpierw stworzyliśmy Menedżera reklam na iOS

Nasz zespół postanowił najpierw opracować wersję aplikacji na iOS, która bardzo dobrze współgrała z React Native, również rozwijaną na iOS. W kolejnych miesiącach powiększyliśmy zespół z trzech do ośmiu inżynierów. Nowi rekruci nie byli zaznajomieni z Reactem — a niektórzy z nich nie byli zaznajomieni z JavaScript — ale chcieli stworzyć wspaniałe środowisko mobilne dla naszych reklamodawców i szybko się powiększyli.

doświadczeni inżynierowie systemu iOS w zespole React Native pomogli nam połączyć funkcje, które nie były jeszcze dostępne w React Native, takie jak zapewnienie dostępu do rolki z aparatu w telefonie. Facebook pomógł nam również połączyć aplikację z niektórymi istniejącymi bibliotekami iOS, które były już używane w innych aplikacjach Facebooka do uwierzytelniania, analizy, raportowania awarii, sieci i powiadomień push. To pozwoliło naszemu zespołowi skupić się na budowaniu tylko produktu.

jak wspomniano powyżej, byliśmy w stanie ponownie wykorzystać wiele naszych wcześniej istniejących bibliotek JavaScript. Jedną z takich bibliotek jest Relay, framework Facebooka do dostarczania danych do aplikacji Reactowych za pośrednictwem GraphQL. Inny zestaw bibliotek zajmuje się internacjonalizacją i lokalizacją, co może być trudne, gdy zaangażowane są strefy czasowe i waluty. Zwykle biblioteki te ładują odpowiednią konfigurację z punktu końcowego JSON na stronie internetowej. Napisaliśmy skrypty do eksportowania plików JSON dla wszystkich obsługiwanych lokalizacji, dołączyliśmy pliki do aplikacji za pomocą zlokalizowanych pakietów iOS, a następnie ujawniliśmy dane JSON do JavaScript za pomocą kilku linii kodu natywnego. Dzięki temu nasze biblioteki mogły działać niemal bez zmian.

jednym z większych wyzwań były przepływy nawigacyjne. Aby poruszać się po istniejących reklamach i kampaniach reklamodawcy, potrzebowaliśmy paska nawigacyjnego. Do tworzenia reklam potrzebowaliśmy paska nawigacyjnego w stylu kreatora. Ponadto ważne było również, aby animacje przejścia i gesty dotykowe były prawidłowe, w przeciwnym razie aplikacja czułaby się bardziej jak uwielbiona witryna mobilna niż natywna aplikacja.

naszym rozwiązaniem był komponent Navigator, który został udostępniony wraz z React Native w katalogu CustomComponents. Zasadniczo jest to komponent Reactowy, który śledzi zbiór innych komponentów Reactowych w stosie. Może wyświetlać jeden z tych komponentów i animować między nimi na podstawie naciśnięć przycisków lub gestów dotykowych. Posiada również wbudowany komponent paska nawigacyjnego, który pozwala nam zaimplementować pasek nawigacyjny podobny do iOS dla większości regularnych widoków, okruszki chleba do nawigacji reklam i kampanii oraz kreator-podobny stepper do przepływu tworzenia. Komponent paska nawigacyjnego jest powiadamiany o postępie animacji i może wykonać niezbędny przyrost animacji, aby go dopasować. Oznacza to, że wszystkie animacje, zarówno dla widoków, jak i pasków nawigacji, są obliczane w JavaScript, ale testy wykazały, że nadal byliśmy w stanie wykonać je przy 60 fps.

jest tylko jeden sposób, aby animacje nawigacyjne mogły się zacinać, i to wtedy, gdy wątek JavaScript został zablokowany podczas dużej operacji. Kiedy napotkaliśmy ten scenariusz, było to prawie wyłącznie spowodowane przetwarzaniem dużych ilości nowo pobranych danych. Oczywiście ma sens, że podczas przechodzenia do nowego widoku należy załadować i przetworzyć więcej danych. W wystarczająco szybkiej sieci proces ten może z łatwością zakłócać trwającą jeszcze animację nawigacyjną. Naszym rozwiązaniem było wyraźne opóźnienie przetwarzania danych do czasu ukończenia animacji, przy użyciu komponentu InteractionManager, który jest również dostarczany jako część React Native. Najpierw animowaliśmy widok zawierający elementy zastępcze, a następnie pozwalaliśmy Relay na przetwarzanie danych, co automatycznie powodowało ponowne renderowanie niezbędnych komponentów Reactowych.

Wysyłka wersji dla Androida

Kiedy Ads Manager dla iOS był bliski wysyłki, zaczęliśmy szukać stworzenia wersji dla Androida tej samej aplikacji. React natywny port dla Androida wydawał się najlepszym sposobem, aby to działało. Na szczęście zespół React Native już ciężko pracował nad tym. Oczywiście chcieliśmy ponownie wykorzystać jak najwięcej kodu aplikacji. Nie tylko logika biznesowa, ale także kod UI, ponieważ większość widoków była w dużej mierze taka sama, z wyjątkiem pewnej stylizacji. Oczywiście były miejsca, w których wersja Androida musiała wyglądać i czuć się inaczej niż wersja iOS, na przykład pod względem nawigacji lub używania natywnych elementów interfejsu użytkownika dla selektorów dat, przełączników itp.

na szczęście funkcja listy bloków React Native packager i mechanizm abstrakcji Reacta bardzo nam pomogły w maksymalizacji ponownego użycia kodu na obu platformach i minimalizacji potrzeby jawnego sprawdzania platformy. Na iOS powiedzieliśmy pakowaczowi, aby zignorował wszystkie pliki kończące się na.android.js. W przypadku rozwoju Androida zignorował wszystkie pliki kończące się na.ios.js. Teraz możemy zaimplementować ten sam komponent raz na Androida i raz na iOS, podczas gdy zużywający się kod byłby nieświadomy platformy. Zamiast więc wprowadzać jawne kontrole if / else dla platformy, próbowaliśmy przekształcić specyficzne dla platformy części interfejsu użytkownika w oddzielne komponenty, które będą miały implementację Androida i iOS. W momencie wysyłki Menedżera reklam dla Androida takie podejście przyniosło około 85 procent ponownego użycia kodu aplikacji.

większym wyzwaniem, przed którym stanęliśmy, było zarządzanie kodem źródłowym. Bazy kodowe dla Androida i iOS były zarządzane w dwóch różnych repozytoriach na Facebooku. Kod źródłowy Menedżera reklam na iOS mieszkał oczywiście w repozytorium iOS, podczas gdy kod wersji Androida musiałby żyć w repozytorium Androida z różnych powodów. Na przykład, podobnie jak w przypadku wersji na iOS, chcieliśmy skorzystać z kilku bibliotek Androida Facebooka, które żyły w repozytorium Androida. Ponadto wszystkie narzędzia do tworzenia, automatyzacji i ciągłej integracji dla aplikacji na Androida zostały podłączone do repozytorium Androida. Biorąc pod uwagę, że port Android aplikacji wymagał refaktoryzacji istniejącego kodu iOS, aby abstrakcyjnie komponenty specyficzne dla platformy do własnych plików, zasadniczo nieustannie rozwidlaliśmy i łączyliśmy dwie wersje tego samego kodu. Wydawało nam się to nie do przyjęcia.

w końcu zdecydowaliśmy się wyznaczyć repozytorium iOS jako źródło prawdy, głównie dlatego, że już tam było, a wersja aplikacji na iOS była najbardziej dojrzała. Ustawiliśmy cronjob, który synchronizował cały kod JavaScript z IOS do repozytorium Androida wiele razy dziennie. Odradzano zatwierdzanie kodu JavaScript do repozytorium Androida i było dozwolone tylko wtedy, gdy następowało dołączenie do repozytorium iOS. Jeśli skrypt synchronizacji wykrył rozbieżność, zgłosił zadanie do dalszego zbadania.

umożliwiliśmy również serwerowi JavaScript packager uruchamianie kodu Androida z repozytorium iOS. W ten sposób nasi programiści, którzy dotykali głównie JavaScript i nie mieli kodu natywnego, mogli rozwijać i testować swoje zmiany zarówno na iOS, jak i Androidzie bezpośrednio z repozytorium iOS. Ale to nadal wymagało od nich zbudowania natywnych części aplikacji na Androida z repozytorium Androida, i to samo dla aplikacji na iOS-ogromny podatek przy testowaniu zmian na dwóch platformach. Aby przyspieszyć przepływ dla programistów tylko w języku JavaScript, zbudowaliśmy również skrypt, który pobrał odpowiednie natywne pliki binarne z naszych serwerów ciągłej integracji. To sprawiło, że nie było potrzeby nawet przechowywania klonu repozytorium Androida dla większości programistów-mogli oni zrobić cały swój rozwój JavaScript ze źródła prawdy w repozytorium iOS i iterację tak szybko, jak lub szybciej niż na stosie internetowym Facebooka.

czego się nauczyliśmy

zespół React Native opracował platformę wraz z naszą aplikacją i ujawnił rodzime komponenty i interfejsy API, których potrzebowaliśmy, aby to osiągnąć. Te komponenty będą korzystne dla wszystkich budujących aplikację w przyszłości. Nawet gdybyśmy sami musieli zbudować kilka komponentów, używanie React Native zamiast czystego natywnego byłoby tego warte. I tak musielibyśmy napisać te komponenty, i prawdopodobnie nie byłyby one wielokrotnego użytku przez inne zespoły w dół drogi.

jedną z lekcji, której się nauczyliśmy, było to, że praca w oddzielnych repozytoriach kodu iOS i Android jest trudna, nawet przy dużej ilości narzędzi i automatyzacji. Kiedy tworzyliśmy aplikację, Facebook korzystał z tego modelu i wszystkie nasze procesy automatyzacji kompilacji i programistów zostały wokół niego skonfigurowane. Jednak nie działa to dobrze w przypadku produktu, który w przeważającej części ma jedną wspólną bazę kodu JavaScript. Na szczęście Facebook przenosi się do ujednoliconego repozytorium dla obu platform — konieczna będzie tylko jedna kopia wspólnego kodu JavaScript, a synchronizacja przejdzie do przeszłości.

kolejna lekcja dotyczyła testowania. Podczas wprowadzania zmian każdy inżynier musi być ostrożny, aby testować na obu platformach, a Proces jest podatny na błędy ludzkie. Ale to tylko nieunikniona konsekwencja opracowania wieloplatformowej aplikacji z tego samego kodu. Mimo to, koszt sporadycznych wpadek z powodu niewystarczających testów jest znacznie przewyższany przez wydajność rozwoju uzyskaną dzięki użyciu React Native i możliwości ponownego użycia kodu na obu platformach. Należy pamiętać, że ta lekcja dotyczy nie tylko inżynierów produktu, ale także inżynierów react Native platform pracujących w Objective-C i Javie. Znaczna część pracy tych inżynierów nie ogranicza się wyłącznie do odpowiednich języków ojczystych. Może również wpływać na JavaScript-na przykład komponentowe interfejsy API lub częściowo współdzielone implementacje. Natywni inżynierowie iOS zazwyczaj nie są przyzwyczajeni do testowania zmian na Androidzie, a odwrotnie jest w przypadku inżynierów Androida. Jest to głównie Luka kulturowa, która wymagała czasu i wysiłku, aby ją zamknąć, a w rezultacie z czasem nasza stabilność wzrosła.

rozwiązaliśmy również problem, budując testy integracyjne, które uruchamiałyby się przy każdej rewizji. Chociaż działało to po wyjęciu z pudełka w celu wykrycia problemów z iOS na iOS i podobnie na Androida, nasze systemy ciągłej integracji nie zostały skonfigurowane do uruchamiania testów Androida na wersjach iOS i odwrotnie. To wymagało wysiłku inżynieryjnego, aby go rozwiązać, a nadal istnieje wystarczająco duży margines błędu, aby od czasu do czasu zepsuć aplikację.

Kiedy wszystko zostało powiedziane i zrobione, nasz Zakład się opłacił — byliśmy w stanie wysłać pierwszą w pełni reagującą natywną aplikację Facebooka na dwóch platformach, z natywnym wyglądem i stylem, zbudowaną przez ten sam zespół inżynierów JavaScript. Nie wszyscy inżynierowie byli zaznajomieni z Reactem, gdy dołączyli do zespołu, ale w ciągu zaledwie pięciu miesięcy stworzyli aplikację na iOS z natywnym wyglądem. A po dodatkowych trzech miesiącach wydaliśmy wersję aplikacji na Androida.

aby być bardziej inkluzywnym w naszym języku, edytowaliśmy ten post, aby zastąpić czarną listę listą bloków.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *