eerder dit jaar introduceerden we React Native voor iOS. React Native brengt wat ontwikkelaars gewend zijn van React op het web-declaratieve zelfstandige UI-componenten en snelle ontwikkelingscycli-naar het mobiele platform, met behoud van de snelheid, trouw en het gevoel van native applicaties. Vandaag zijn we blij om React Native voor Android vrij te geven.
op Facebook gebruiken we nu al meer dan een jaar React Native in productie. Bijna precies een jaar geleden heeft ons team de Ads Manager app ontwikkeld. Ons doel was om een nieuwe app te maken om de miljoenen mensen die adverteren op Facebook hun accounts te laten beheren en nieuwe advertenties te maken onderweg. Het eindigde als niet alleen Facebook ‘ s eerste volledig React Native app, maar ook de eerste cross-platform een. In deze post willen we graag met u delen hoe we deze app hebben gebouwd, hoe React Native ons in staat stelde om sneller te bewegen, en de lessen die we hebben geleerd.
kiezen voor React Native
niet zo lang geleden was React Native nog een nieuwe technologie die niet bewezen was in de productie. Terwijl het ontwikkelen van een nieuwe app op basis van deze technologie droeg een aantal risico ‘ s, het werd gecompenseerd door de potentiële voordelen.
ten eerste was ons eerste team van drie productingenieurs al bekend met React. Ten tweede moest de app veel complexe bedrijfslogica bevatten om nauwkeurig om te gaan met verschillen in advertentieformaten, tijdzones, datumformaten, valuta ‘ s, valutaconventies, enzovoort. Veel hiervan was al geschreven in JavaScript. Het vooruitzicht van het schrijven van al die code in Objective-C alleen om later te schrijven in Java voor de Android — versie van de app was niet aantrekkelijk-noch zou het efficiënt zijn. Ten derde, in React Native zou het gemakkelijk zijn om de meeste van de UI-oppervlakken die we wilden bouwen te implementeren-het weergeven van veel gegevens in de vorm van lijsten, tabellen of grafieken. Productingenieurs kunnen onmiddellijk productief zijn om deze standpunten te implementeren, zolang ze weten dat ze reageren.
natuurlijk vormden sommige functies een uitdaging voor dit nieuwe platform — bijvoorbeeld de afbeeldingsbewerker, waarmee adverteerders inzoomen en foto ‘ s bijsnijden, en de kaartweergave, waarmee adverteerders zich richten op mensen binnen een bepaalde straal van een locatie. Een ander voorbeeld is de breadcrumb navigatie, die helpt adverteerders visualiseren de hiërarchie van advertenties in hun accounts. Dit bood ons mogelijkheden om het platform verder te duwen.
Building Ads Manager for iOS first
ons team besloot eerst een iOS-versie van de app te ontwikkelen, die zeer goed overeenkwam met React Native die ook als eerste werd ontwikkeld voor iOS. We groeiden het team van drie naar acht ingenieurs in de volgende maanden. De nieuwe rekruten waren niet bekend met React — en sommigen van hen waren niet bekend met JavaScript-maar ze stonden te popelen om een geweldige mobiele ervaring op te bouwen voor onze adverteerders, en ze stegen snel op.
ervaren iOS-ingenieurs van het React Native-team hielpen ons bij het overbruggen van functies die nog niet beschikbaar waren in React Native, zoals het bieden van toegang tot de camerarol van de telefoon. Ze hielpen ons ook de app te bundelen met een aantal van Facebook ‘ s bestaande iOS-bibliotheken die al werden gebruikt in andere Facebook-apps voor het uitvoeren van authenticatie, analytics, Crashrapportage, netwerken en pushmeldingen. Dat laat ons team zich richten op het bouwen van alleen het product.
zoals hierboven vermeld, konden we veel van onze reeds bestaande JavaScript-bibliotheken hergebruiken. Een dergelijke bibliotheek is Relay, Facebook framework voor het leveren van gegevens om applicaties te reageren via GraphQL. Een andere reeks bibliotheken behandelde internationalisering en lokalisatie, wat lastig kan zijn wanneer tijdzones en valuta ‘ s betrokken zijn. Normaal gesproken Laden deze bibliotheken de juiste configuratie van een JSON-eindpunt op de website. We schreven scripts om de JSON-bestanden te exporteren voor alle ondersteunde locales, opgenomen de bestanden met de app met behulp van IOS ‘ s gelokaliseerde bundels, en vervolgens blootgesteld de JSON-gegevens JavaScript met een paar regels native code. Hierdoor konden onze bibliotheken vrijwel ongewijzigd werken.
een van de grotere uitdagingen waar we voor stonden waren de navigatiestromen. Voor het navigeren door de bestaande advertenties en campagnes van een adverteerder, wilden we een breadcrumb navigatiebalk. Voor de advertentiecreatiestroom hadden we een navigatiebalk in wizard-stijl nodig. Op de top van dat, het was ook cruciaal om de overgang animaties en touch gebaren goed te krijgen, anders zou de app meer hebben gevoeld als een verheerlijkte mobiele website dan een native app.
onze oplossing was de Navigator component, die beschikbaar werd gemaakt samen met React Native onder de CustomComponents directory. In essentie is het een React component die een reeks andere React componenten in een stapel bijhoudt. Het kan een van deze componenten weer te geven en animeren tussen hen op basis van druk op de knop of touch gebaren. Het heeft ook een pluggable navigatiebalk component, waarmee we implementeren van een iOS-achtige navigatiebalk voor de meeste regelmatige weergaven, broodkruimels voor het navigeren advertenties en campagnes, en een wizard-achtige stepper voor de creatie stroom. De navigatiebalk component wordt op de hoogte van de animatie vooruitgang en kan de nodige animatie increment uit te voeren aan te passen. Dit betekent dat alle animaties, zowel voor de weergaven als voor de navigatiebalken, worden berekend in JavaScript, maar tests toonden aan dat we nog steeds in staat waren om ze uit te voeren op 60 fps.
Er is maar één manier waarop navigatie-animaties konden stotteren, en dat is wanneer de JavaScript-thread werd geblokkeerd tijdens een grote operatie. Toen we dit scenario tegenkwamen, was het bijna uitsluitend te wijten aan het verwerken van grote hoeveelheden nieuw opgehaalde gegevens. Natuurlijk is het logisch dat wanneer u naar een nieuwe weergave navigeert, meer gegevens moeten worden geladen en verwerkt. Op een voldoende snel netwerk kan dat proces gemakkelijk interfereren met een navigatieanimatie die nog aan de gang is. Onze oplossing was om de dataverwerking expliciet uit te stellen tot animaties compleet waren, met behulp van de InteractionManager component, die ook wordt geleverd als onderdeel van React Native. We zouden eerst animeren naar een uitzicht dat placeholders bevatte en dan laat Relay doen de gegevensverwerking, die automatisch veroorzaakt dat de noodzakelijke React componenten opnieuw te renderen.
verzenden een Android-versie
toen Ads Manager voor iOS dicht bij verzending was, begonnen we te kijken naar het bouwen van een Android-versie van dezelfde app. Een React Native port naar Android leek de beste manier om dat te laten werken. Gelukkig was het React Native team al hard aan het werk om dat te creëren. Uiteraard wilden we zoveel mogelijk app code hergebruiken. Niet alleen de business logica, maar ook de UI-code, omdat de meeste van de standpunten waren grotendeels hetzelfde, behalve voor sommige styling. Natuurlijk, er waren plaatsen waar de Android-versie moest kijken en voelen anders dan de iOS-versie, bijvoorbeeld, in termen van navigatie of het gebruik van native UI-elementen voor datumkiezers, schakelaars, enz.
gelukkig hebben de block list-functie van de React Native packager en het abstractiemechanisme van React ons veel geholpen met het maximaliseren van code-hergebruik over de twee platforms en het minimaliseren van de behoefte aan expliciete platformcontroles. Op iOS, we vertelde de verpakker om alle bestanden die eindigen in negeren .Android.js. Voor Android ontwikkeling, het genegeerd alle bestanden die eindigen in .ios.js. Nu konden we dezelfde component eenmaal implementeren voor Android en eenmaal voor iOS, terwijl de consumerende code zich niet bewust zou zijn van het platform. Dus in plaats van de invoering van expliciete if/else controles voor het platform, we geprobeerd om platform-specifieke delen van de UI refactor in afzonderlijke componenten die een Android en iOS implementatie zou hebben. Op het moment van verzending Ads Manager voor Android, die aanpak leverde ongeveer 85 procent hergebruik van app code.
een grotere uitdaging waar we voor stonden was hoe we de broncode moesten beheren. Android en iOS codebases werden beheerd in twee verschillende repositories op Facebook. De broncode voor Ads Manager voor iOS woonde in de iOS-repository, natuurlijk, terwijl de code voor de Android-versie zou moeten leven in de Android-repository om verschillende redenen. Bijvoorbeeld, net als bij de iOS-versie, we wilden gebruik maken van een paar van Facebook ‘ s Android bibliotheken, die leefde in de Android repository te maken. Daarnaast werden alle bouwtools, automatisering en continue integratie voor Android-apps aangesloten op de Android-repository. Gezien het feit dat de Android-poort van de app nodig refactoring bestaande iOS-code abstract platform-specifieke componenten in hun eigen bestanden, zouden we in wezen voortdurend forking en samenvoegen van twee versies van dezelfde codebase. Dat leek ons een onaanvaardbare situatie.
uiteindelijk hebben we besloten om de iOS repository aan te wijzen als de bron van de waarheid, vooral omdat het er al was en de iOS-versie van de app de meest volwassen was. We hebben een cronjob opgezet die vele malen per dag alle JavaScript-code van de iOS naar de Android-repository synchroniseerde. Het committen van JavaScript naar de Android repository werd afgeraden en was alleen toegestaan als het werd opgevolgd met een bijbehorende commit naar de iOS repository. Als het synchronisatiescript een discrepantie ontdekte, diende het een taak in voor verder onderzoek.
we maakten het ook mogelijk voor de JavaScript packager server om Android code uit te voeren vanuit de iOS repository. Op die manier konden onze productontwikkelaars, die vooral JavaScript en geen native code raakten, hun wijzigingen ontwikkelen en testen op zowel iOS als Android rechtstreeks vanuit de iOS-repository. Maar dat nog steeds vereist dat ze hebben gebouwd van de inheemse delen van de Android-app van de Android-repository, en hetzelfde voor de iOS-app-een enorme belasting bij het testen van wijzigingen op twee platforms. Om de stroom voor JavaScript-only ontwikkelaars te versnellen, hebben we ook een script gebouwd dat de juiste native binary downloadde van onze continue integratieservers. Dit maakte het onnodig om zelfs een kloon van de Android — repository voor de meeste ontwikkelaars te houden-ze konden al hun JavaScript-ontwikkeling doen vanuit de bron van de waarheid in de iOS-repository en itereren zo snel als of sneller dan op Facebook ‘ s web stack.
wat we geleerd hebben
Het React Native team ontwikkelde het platform Naast onze app, en legde de native componenten en API ‘ s bloot die we nodig hadden om het mogelijk te maken. Die componenten zullen iedereen ten goede komen bij het bouwen van een app in de toekomst. Zelfs als we zelf een paar componenten hadden moeten bouwen, zou het gebruik van React Native over pure native still het waard zijn geweest. We zouden die componenten toch moeten schrijven, en ze zouden waarschijnlijk niet herbruikbaar zijn geweest door andere teams verderop.
een les die we geleerd hebben was dat het werken met afzonderlijke iOS-en Android-codeopslagplaatsen moeilijk is, zelfs met veel tools en automatisering. Toen we de app bouwden, gebruikte Facebook Dit model, en al onze bouwautomatisering en ontwikkelaarsprocessen werden er omheen opgezet. Echter, het werkt niet goed voor een product dat, Voor het grootste deel, heeft een enkele gedeelde JavaScript codebase. Gelukkig, Facebook verhuist naar een uniforme repository voor beide platforms — slechts een kopie van de gemeenschappelijke JavaScript-code nodig zal zijn, en synchronisatie zal een ding van het verleden.
een andere les die we leerden betrof testen. Bij het maken van veranderingen, moet elke ingenieur voorzichtig zijn om te testen op beide platforms, en het proces is gevoelig voor menselijke fouten. Maar dat is gewoon een onvermijdelijk gevolg van het ontwikkelen van een cross-platform app van dezelfde codebase. Dat gezegd hebbende, de kosten van een incidenteel ongeluk als gevolg van onvoldoende testen is veel opweegt tegen de ontwikkeling efficiëntie opgedaan door het gebruik van React Native en de mogelijkheid om code te hergebruiken op beide platforms in de eerste plaats. Houd in gedachten, deze les is niet alleen van toepassing op product engineers; het is ook van toepassing op de React Native platform engineers werken in Objective-C en Java. Veel van het werk dat deze ingenieurs doen is niet louter beperkt tot de respectieve moedertaal. Het kan ook van invloed zijn op JavaScript – bijvoorbeeld component API ‘ s of gedeeltelijk gedeelde implementaties. Native iOS-ingenieurs zijn meestal niet gewend om veranderingen op Android te testen, en het omgekeerde geldt voor Android-ingenieurs. Dit is vooral een culturele kloof die tijd en moeite kostte om te dichten, en als gevolg daarvan is onze stabiliteit in de loop van de tijd toegenomen.
we hebben het probleem ook aangepakt door integratietests op te bouwen die bij elke revisie zouden worden uitgevoerd. Hoewel dit werkte out of the box voor het vangen van iOS-problemen op iOS en ook voor Android, onze continue integratiesystemen waren niet ingesteld om Android-tests op iOS revisies en vice versa uit te voeren. Dit kostte technische inspanning op te lossen, en er is nog steeds een groot genoeg foutmarge om af en toe breken van de app.
toen alles gezegd en gedaan was, heeft onze weddenschap zijn vruchten afgeworpen — we waren in staat om Facebook ‘ s eerste volledig React Native app op twee platforms te verzenden, met native look and feel, gebouwd door hetzelfde team van JavaScript engineers. Niet alle ingenieurs waren bekend met React toen ze bij het team kwamen, maar ze bouwden een iOS-app met native look and feel in slechts vijf maanden. Na nog eens drie maanden brachten we de Android-versie van de app uit.
in een poging om meer inclusief te zijn in onze taal, hebben we dit bericht bewerkt om zwarte lijst te vervangen door blok lijst.