tidigare i år introducerade vi React Native för iOS. React Native ger vad utvecklare är vana vid från React on the web-deklarativa fristående UI-komponenter och snabba utvecklingscykler — till mobilplattformen, samtidigt som de behåller hastigheten, troheten och känslan av inbyggda applikationer. Idag är vi glada att släppa React Native för Android.
på Facebook har vi använt React Native i produktion i över ett år nu. För nästan exakt ett år sedan bestämde vårt team sig för att utveckla Ads Manager-appen. Vårt mål var att skapa en ny app för att låta miljontals människor som annonserar på Facebook hantera sina konton och skapa nya annonser på språng. Det slutade inte bara Facebook: s första helt React Native app utan också den första plattformen. I det här inlägget vill vi dela med dig hur vi byggde den här appen, hur React Native gjorde det möjligt för oss att gå snabbare och de lärdomar vi lärde oss.
välja React Native
För länge sedan var React Native fortfarande en ny teknik som inte hade bevisats i produktionen. Samtidigt utveckla en ny app baserad på denna teknik medfört viss risk, det uppvägdes av de potentiella upsides.
först var vårt första team av tre produktingenjörer redan bekant med React. För det andra behövde appen innehålla mycket komplex affärslogik för att exakt hantera skillnader i annonsformat, tidszoner, datumformat, valutor, valutakonventioner och så vidare. Mycket av detta skrevs redan i JavaScript. Utsikterna att skriva all den koden i Objective-C bara för att senare skriva den i Java för Android — versionen av appen var inte tilltalande-det skulle inte heller vara effektivt. För det tredje, i React Native skulle det vara lätt att implementera de flesta av UI — ytorna vi ville bygga-visa massor av data i form av listor, tabeller eller grafer. Produktingenjörer kan vara omedelbart produktiva genomföra dessa åsikter, så länge de visste React.naturligtvis presenterade vissa funktioner en utmaning för den här nya plattformen — till exempel bildredigeraren, som låter annonsörer zooma och beskära ett foto och kartvyn, som låter annonsörer rikta in sig på personer inom en viss radie av en plats. Ett annat exempel är brödsmulnavigeringen, som hjälper annonsörer att visualisera hierarkin för annonser i sina konton. Dessa gav oss möjligheter att driva plattformen ytterligare.
Building Ads Manager för iOS first
vårt team bestämde sig för att utveckla en iOS-version av appen först, vilket justerade mycket bra med React Native som också utvecklades först för iOS. Vi växte laget från tre till åtta ingenjörer under de följande månaderna. De nya rekryterna var inte bekanta med React — och några av dem var inte bekanta med JavaScript-men de var angelägna om att bygga en bra mobilupplevelse för våra annonsörer, och de ramped upp snabbt.
erfarna iOS-ingenjörer på React Native-teamet hjälpte oss att överbrygga funktioner som ännu inte var tillgängliga i React Native, till exempel att ge åtkomst till telefonens kamerarulle. De hjälpte oss också att bunta appen med några av Facebook: s befintliga Facebook-bibliotek som redan användes i andra Facebook-appar för att utföra autentisering, analys, kraschrapportering, nätverk och push-meddelanden. Det låter vårt team fokusera på att bygga bara produkten.
Som nämnts ovan kunde vi återanvända många av våra befintliga JavaScript-bibliotek. Ett sådant bibliotek är Relay, Facebook: s ramverk för att leverera data för att reagera applikationer via GraphQL. En annan uppsättning bibliotek handlade om internationalisering och lokalisering, vilket kan vara svårt när tidszoner och valutor är inblandade. Normalt laddar dessa bibliotek rätt konfiguration från en JSON-slutpunkt på webbplatsen. Vi skrev skript för att exportera JSON-filerna för alla språk som stöds, inkluderade filerna med appen med iOS: s lokaliserade buntar och exponerade sedan JSON-data till JavaScript med några rader med inbyggd kod. Detta gjorde det möjligt för våra bibliotek att arbeta nästan oförändrat.
en av de större utmaningarna vi mötte var navigationsflödena. För att navigera i annonsörens befintliga annonser och kampanjer ville vi ha ett navigeringsfält för brödsmulor. För annonsflödet behövde vi en navigeringsfält i guidestil. Dessutom var det också viktigt att få övergångsanimationerna och röra gester rätt, annars skulle appen ha känt sig mer som en förhärligad mobilwebbplats än en inbyggd app.
vår lösning var Navigatorkomponenten, som gjordes tillgänglig tillsammans med React Native under CustomComponents-katalogen. I huvudsak är det en React-komponent som håller reda på en uppsättning andra React-komponenter i en stapel. Den kan visa en av dessa komponenter och animera mellan dem baserat på knapptryckningar eller beröringsgester. Den har också en pluggbar navigeringsfältkomponent, som låter oss implementera en iOS-liknande navigeringsfält för de flesta vanliga vyer, brödsmulor för att navigera i annonser och kampanjer och en guideliknande steg för skapningsflödet. Navigeringsfältet komponenten meddelas om animation framsteg och kan utföra den nödvändiga animation steg för att matcha. Det betyder att alla animationer, både för vyerna och för navigeringsfälten, beräknas i JavaScript, men tester visade att vi fortfarande kunde utföra dem vid 60 fps.
det finns bara ett sätt att navigationsanimationer kan stamma, och det var då JavaScript-tråden blockerades under en stor operation. När vi stötte på detta scenario berodde det nästan uteslutande på att vi behandlade stora mängder nyhämtade data. Naturligtvis är det vettigt att när du navigerar till en ny vy måste mer data laddas och bearbetas. På ett tillräckligt snabbt nätverk kan den processen lätt störa en navigeringsanimation som fortfarande pågår. Vår lösning här var att uttryckligen fördröja databehandlingen tills animationerna var färdiga, med hjälp av InteractionManager-komponenten, som också skickas som en del av React Native. Vi skulle först animera till en vy som innehöll platshållare och sedan låta Relay göra databehandlingen, vilket automatiskt orsakade de nödvändiga React-komponenterna att återge.
Frakt en Android-version
När Ads Manager för iOS var nära frakt började vi titta på att bygga en Android-version av samma app. En React Native port till Android verkade som det bästa sättet att få det att fungera. Lyckligtvis var React Native-teamet redan hårt på jobbet och skapade just det. Naturligtvis ville vi återanvända så mycket appkod som möjligt. Inte bara affärslogiken utan också UI-koden, eftersom de flesta vyerna i stort sett var desamma, spara för lite styling. Självklart, det fanns platser där Android-versionen behövde se ut och känna sig annorlunda än iOS-versionen, till exempel, när det gäller navigering eller användning av inbyggda UI-element för datumplockare, switchar, etc.lyckligtvis hjälpte React Native packagers blocklistfunktion och reacts abstraktionsmekanism oss mycket med att maximera kodåteranvändning över de två plattformarna och minimera behovet av explicita plattformskontroller. På iOS, vi berättade packager att ignorera alla filer som slutar i .Android.js. För Android utveckling, ignorerade det alla filer som slutar i .ios.js. Nu kunde vi implementera samma komponent en gång för Android och en gång för iOS, medan den konsumerande koden skulle vara omedveten om plattformen. Så istället för att införa explicit if/else-kontroller för plattformen försökte vi refactor plattformsspecifika delar av användargränssnittet i separata komponenter som skulle ha en Android-och iOS-implementering. Vid tidpunkten för leverans av Ads Manager för Android gav den metoden cirka 85 procent återanvändning av appkod.
en större utmaning som vi mötte var hur man hanterar källkoden. Android-och iOS-kodbaser hanterades i två olika arkiv på Facebook. Källkoden för Ads Manager för iOS bodde naturligtvis i iOS-förvaret, medan koden för Android-versionen måste leva i Android-förvaret av olika skäl. Till exempel, precis som med iOS-versionen, ville vi använda några av Facebook: s Android-bibliotek, som bodde i Android-förvaret. Dessutom var alla byggverktyg, automatisering och kontinuerlig integration för Android-appar anslutna till Android-förvaret. Med tanke på att appens Android-port krävde refactoring av befintlig iOS-kod till abstrakta plattformsspecifika komponenter i sina egna filer, skulle vi i huvudsak ha ständigt förfalskat och sammanfogat två versioner av samma kodbas. Det verkade som en oacceptabel situation för oss.
i slutändan bestämde vi oss för att utse iOS-förvaret som sanningskälla, främst för att det redan var där och iOS-versionen av appen var den mest mogna. Vi satte upp en cronjob som synkroniserade All JavaScript-kod från iOS till Android-förvaret många gånger om dagen. Att begå JavaScript till Android-förvaret avskräcktes och tilläts endast om det följdes upp med ett åtföljande åtagande till iOS-förvaret. Om synkroniseringsskriptet upptäckte en avvikelse lämnade det in en uppgift för vidare utredning.
Vi gjorde det också möjligt för JavaScript packager-servern att köra Android-kod från iOS-förvaret. På så sätt kunde våra produktutvecklare, som berörde mestadels JavaScript och ingen inbyggd kod, utveckla och testa sina ändringar på både iOS och Android direkt från iOS-förvaret. Men det krävde fortfarande att de hade byggt de inbyggda delarna av Android — appen från Android-förvaret, och detsamma för iOS-appen-en enorm skatt när de testade förändringar på två plattformar. För att påskynda flödet för JavaScript-bara utvecklare byggde vi också ett skript som hämtade lämplig inbyggd binär från våra kontinuerliga integrationsservrar. Detta gjorde det onödigt att ens behålla en klon av Android — förvaret för de flesta utvecklare-de kunde göra all sin JavaScript-utveckling från sanningskällan i iOS-förvaret och iterera så fort som eller snabbare än på Facebook: s webbstack.
vad vi lärde oss
React Native-teamet utvecklade plattformen tillsammans med vår app och exponerade de inbyggda komponenterna och API: erna som vi behövde för att få det att hända. Dessa komponenter kommer att gynna alla som bygger en app i framtiden. Även om vi hade varit tvungna att bygga ut några komponenter själva, skulle React Native över pure native fortfarande ha varit värt det. Vi skulle ha haft att skriva dessa komponenter ändå, och de förmodligen inte skulle ha varit återanvändbara av andra lag på vägen.
en lektion vi lärde oss var att det är svårt att arbeta över separata iOS-och Android-kodförråd, även med massor av verktyg och automatisering. När vi byggde appen använde Facebook den här modellen, och alla våra byggautomatiserings-och utvecklingsprocesser inrättades kring den. Det fungerar dock inte bra för en produkt som För det mesta har en enda delad JavaScript-kodbas. Lyckligtvis flyttar Facebook till ett enhetligt arkiv för båda plattformarna — bara en kopia av gemensam JavaScript-kod kommer att behövas, och synkroniseringar kommer att vara en sak av det förflutna.
en annan lektion vi lärde oss gällde testning. Vid ändringar måste varje ingenjör vara noga med att testa på båda plattformarna, och processen är benägen för mänskliga fel. Men det är bara en oundviklig följd av att utveckla en plattformsapplikation från samma kodbas. Som sagt, kostnaden för en tillfällig missöde på grund av otillräcklig testning uppvägs långt av den utvecklingseffektivitet som uppnåtts genom att använda React Native och att kunna återanvända kod över båda plattformarna i första hand. Tänk på att den här lektionen inte bara gäller produktingenjörer; det gäller också React Native platform engineers som arbetar i Objective-C och Java. Mycket av det arbete som dessa ingenjörer gör är inte enbart begränsat till respektive modersmål. Det kan också påverka JavaScript-till exempel komponents API: er eller delvis delade implementeringar. Native iOS ingenjörer är vanligtvis inte vana vid att behöva testa förändringar på Android, och det omvända gäller för Android ingenjörer. Detta är främst ett kulturellt gap som tog tid och ansträngning att stänga, och som ett resultat har vår stabilitet över tiden ökat.
vi tog också upp problemet genom att bygga integrationstester som skulle köras vid varje revision. Även om detta fungerade ur lådan för att fånga iOS-problem på iOS och på samma sätt för Android, var våra kontinuerliga integrationssystem inte inrättade för att köra Android-tester på iOS-revisioner och vice versa. Detta tog tekniska ansträngningar att lösa, och det finns fortfarande en tillräckligt stor felmarginal för att ibland bryta appen.
När allt var sagt och gjort, betalade vår satsning – vi kunde skicka Facebook: s första helt React Native app på två plattformar, med inbyggt utseende och känsla, byggt av samma team av JavaScript-ingenjörer. Inte alla ingenjörer var bekanta med React när de gick med i laget, men de byggde en iOS-app med inbyggt utseende och känsla på bara fem månader. Och efter ytterligare tre månader släppte vi Android-versionen av appen.
i ett försök att vara mer inkluderande på vårt språk har vi redigerat det här inlägget för att ersätta svart lista med blocklista.