la începutul acestui an, am introdus React Native pentru iOS. React Native aduce ceea ce dezvoltatorii sunt folosite pentru a reacționa pe web — declarative componente UI de sine stătătoare și cicluri de dezvoltare rapidă-la platforma mobilă, păstrând în același timp viteza, fidelitatea, și simt de aplicații native. Astăzi, suntem bucuroși să lansăm React Native pentru Android.
la Facebook folosim React Native în producție de peste un an. Cu aproape exact un an în urmă, echipa noastră și-a propus să dezvolte aplicația Ads Manager. Scopul nostru a fost să creăm o nouă aplicație pentru a permite milioanelor de oameni care fac publicitate pe Facebook să își gestioneze conturile și să creeze noi reclame din mers. A ajuns să fie nu numai prima aplicație nativă React pe deplin a Facebook, ci și prima platformă multiplă. În această postare, am dori să vă împărtășim cum am construit această aplicație, cum React Native ne-a permis să ne mișcăm mai repede și lecțiile pe care le-am învățat.
alegerea React Native
nu cu mult timp în urmă, React Native era încă o tehnologie nouă care nu fusese dovedită în producție. În timp ce dezvoltarea unei noi aplicații bazate pe această tehnologie a avut un anumit risc, a fost depășită de potențialele avantaje.
În primul rând, echipa noastră inițială de trei ingineri de produse era deja familiarizată cu React. În al doilea rând, aplicația trebuia să conțină o mulțime de logici complexe de afaceri pentru a gestiona cu exactitate diferențele în formatele de anunțuri, fusurile orare, formatele de date, monedele, convențiile valutare și așa mai departe. O mare parte din acest lucru a fost deja scris în JavaScript. Perspectiva de a scrie tot acest cod în Objective-C doar pentru a — l scrie ulterior în Java pentru versiunea Android a aplicației nu a fost atrăgătoare-și nici nu ar fi eficientă. În al treilea rând, în React Native ar fi ușor să implementăm majoritatea suprafețelor UI pe care am vrut să le construim — afișând o mulțime de date sub formă de liste, tabele sau grafice. Inginerii de produse ar putea fi imediat productivi implementând aceste puncte de vedere, atâta timp cât știau React.desigur ,unele funcții au reprezentat o provocare pentru această nouă platformă — de exemplu, editorul de imagini, care permite agenților de publicitate să mărească și să decupeze o fotografie, și vizualizarea hărții, care permite agenților de publicitate să vizeze persoane pe o anumită rază a unei locații. Un alt exemplu este navigarea breadcrumb, care îi ajută pe agenții de publicitate să vizualizeze ierarhia anunțurilor din conturile lor. Acestea ne-au oferit oportunități de a împinge platforma mai departe.
Building Ads Manager pentru iOS mai întâi
echipa noastră a decis să dezvolte o versiune iOS a aplicației în primul rând, care aliniat foarte bine cu React nativ, de asemenea, în curs de dezvoltare în primul rând pentru iOS. Am crescut echipa de la trei la opt ingineri în următoarele luni. Noii recruți nu erau familiarizați cu React — iar unii dintre ei nu erau familiarizați cu JavaScript — dar erau dornici să construiască o experiență mobilă excelentă pentru agenții noștri de publicitate și s-au intensificat rapid.
inginerii iOS experimentați din echipa React Native ne-au ajutat să îmbunătățim caracteristicile care nu erau încă disponibile în React Native, cum ar fi furnizarea accesului la rola camerei telefonului. De asemenea, ne-au ajutat să conectăm aplicația cu unele dintre bibliotecile iOS existente ale Facebook, care erau deja utilizate în alte aplicații Facebook pentru a efectua autentificarea, analiza, raportarea accidentelor, crearea de rețele și notificările push. Acest lucru a permis echipei noastre să se concentreze pe construirea doar a produsului.așa cum am menționat mai sus, am reușit să reutilizăm o mulțime de biblioteci JavaScript preexistente. O astfel de bibliotecă este Relay, cadrul Facebook pentru furnizarea de date pentru a reacționa aplicații prin GraphQL. Un alt set de biblioteci s-a ocupat de internaționalizare și localizare, care poate fi dificil atunci când sunt implicate fusuri orare și valute. În mod normal, aceste biblioteci încarcă configurația corectă dintr-un punct final JSON de pe site-ul web. Am scris scripturi pentru a exporta fișierele JSON pentru toate locațiile acceptate, Am inclus fișierele cu aplicația folosind pachetele localizate ale iOS și apoi am expus datele JSON la JavaScript cu câteva linii de cod nativ. Acest lucru a permis bibliotecilor noastre să funcționeze aproape neschimbat.
una dintre cele mai mari provocări cu care ne-am confruntat au fost fluxurile de navigație. Pentru navigarea anunțurilor și campaniilor existente ale unui agent de publicitate, am dorit o bară de navigare breadcrumb. Pentru fluxul de creare a anunțurilor, aveam nevoie de o bară de navigare în stil vrăjitor. În plus, a fost, de asemenea, crucial să obțineți animațiile de tranziție și gesturile tactile corecte, altfel aplicația s-ar fi simțit mai mult ca un site mobil glorificat decât o aplicație nativă.
soluția noastră a fost componenta Navigator, care a fost pusă la dispoziție împreună cu React Native în directorul CustomComponents. În esență, este o componentă React care ține evidența unui set de alte componente React într-o stivă. Poate afișa una dintre aceste componente și anima între ele pe baza apăsărilor de butoane sau a gesturilor tactile. De asemenea, are o componentă conectabilă a barei de navigare, care ne permite să implementăm o bară de navigare asemănătoare iOS pentru majoritatea vizualizărilor obișnuite, pesmet pentru navigarea anunțurilor și campaniilor și un pas cu pas asemănător vrăjitorului pentru fluxul de creare. Componenta barei de navigare este notificată cu privire la progresul animației și poate efectua incrementul de animație necesar pentru a se potrivi. Aceasta înseamnă că toate animațiile, atât pentru vizualizări, cât și pentru barele de navigare, sunt calculate în JavaScript, dar testele au arătat că am fost încă capabili să le efectuăm la 60 fps.
există o singură modalitate prin care animațiile de navigare s-ar putea bâlbâi și atunci firul JavaScript a fost blocat în timpul unei operații mari. Când am întâlnit acest scenariu, s-a datorat aproape exclusiv procesării unor cantități mari de date nou preluate. Desigur, are sens că atunci când navigați la o nouă vizualizare, mai multe date trebuie încărcate și procesate. Într-o rețea suficient de rapidă, acest proces ar putea interfera cu ușurință cu o animație de navigare încă în desfășurare. Soluția noastră aici a fost de a întârzia în mod explicit prelucrarea datelor până la finalizarea animațiilor, folosind componenta Interacționămanager, care este livrată și ca parte a React Native. Ne-ar anima mai întâi la o vedere care conținea substituenți și apoi lăsați releu face prelucrarea datelor, care a cauzat în mod automat componentele React necesare pentru a re-face.
expedierea unei versiuni Android
când Ads Manager pentru iOS era aproape de expediere, am început să ne uităm la construirea unei versiuni Android a aceleiași aplicații. Un port nativ React la Android părea cel mai bun mod de a face acest lucru. Din fericire, echipa nativă React lucra deja din greu, creând doar asta. Desigur, am vrut să reutilizăm cât mai mult cod de aplicație posibil. Nu doar logica de afaceri, ci și Codul UI, deoarece majoritatea punctelor de vedere au fost în mare parte aceleași, cu excepția unor stiluri. Desigur, au existat locuri în care versiunea Android trebuia să arate și să se simtă diferit de versiunea iOS, de exemplu, în ceea ce privește navigarea sau utilizarea elementelor UI native pentru culegătorii de date, comutatoare etc.
Din fericire, funcția de listă de blocuri React Native packager și mecanismul de abstractizare React ne-au ajutat foarte mult la maximizarea reutilizării codului pe cele două platforme și la minimizarea nevoii de verificări explicite ale platformei. Pe iOS, I-am spus ambalatorului să ignore toate fișierele care se termină.android.js. Pentru dezvoltarea Android, a ignorat toate fișierele care se termină în .ios.js. Acum am putea implementa aceeași componentă o dată pentru Android și o dată pentru iOS, în timp ce codul consumator ar fi uitat de platformă. Deci, în loc să introducem verificări explicite if/else pentru platformă, am încercat să refactorizăm părți specifice platformei UI în componente separate care ar avea o implementare Android și iOS. La momentul expedierii Ads Manager pentru Android, această abordare a generat o reutilizare de aproximativ 85% a codului aplicației.
o provocare mai mare cu care ne-am confruntat a fost cum să gestionăm codul sursă. Bazele de cod Android și iOS au fost gestionate în două depozite diferite de pe Facebook. Codul sursă pentru Ads Manager pentru iOS a trăit în depozitul iOS, desigur, în timp ce codul pentru versiunea Android ar trebui să trăiască în depozitul Android din diverse motive. De exemplu, la fel ca în cazul versiunii iOS, am vrut să folosim câteva dintre bibliotecile Android Facebook, care trăiau în depozitul Android. În plus, toate instrumentele de construire, automatizarea și integrarea continuă pentru aplicațiile Android au fost conectate la depozitul Android. Având în vedere că portul Android al aplicației a necesitat refactorizarea codului iOS existent la componentele abstracte specifice platformei în propriile fișiere, în esență, am fi bifurcat și fuzionat în mod constant două versiuni ale aceleiași baze de cod. Părea o situație inacceptabilă pentru noi.
în cele din urmă, am decis să desemnăm depozitul iOS ca sursă a adevărului, mai ales pentru că era deja acolo și versiunea iOS a aplicației era cea mai matură. Am creat un cronjob care sincroniza tot codul JavaScript de la iOS la depozitul Android de multe ori pe zi. Comiterea JavaScript la depozitul Android a fost descurajată și a fost permisă numai dacă a fost urmată de o comitere însoțitoare la depozitul iOS. Dacă scriptul de sincronizare a detectat o discrepanță, a depus o sarcină pentru investigații suplimentare.
De asemenea, am făcut posibil ca serverul JavaScript packager să ruleze codul Android din depozitul iOS. În acest fel, dezvoltatorii noștri de produse, care au atins mai ales JavaScript și niciun cod nativ, și-ar putea dezvolta și testa modificările atât pe iOS, cât și pe Android direct din depozitul iOS. Dar asta le — a cerut totuși să fi construit părțile native ale aplicației Android din depozitul Android și același lucru pentru aplicația iOS-o taxă uriașă la testarea modificărilor pe două platforme. Pentru a accelera fluxul pentru dezvoltatorii numai JavaScript, am construit, de asemenea, un script care a descărcat binarul nativ adecvat de pe serverele noastre de integrare continuă. Acest lucru a făcut inutil să păstrați chiar și o clonă a depozitului Android pentru majoritatea dezvoltatorilor — aceștia ar putea să-și facă toată dezvoltarea JavaScript de la sursa adevărului în depozitul iOS și să itereze la fel de repede sau mai repede decât pe stiva web a Facebook.
ce am învățat
echipa React Native a dezvoltat platforma alături de aplicația noastră și a expus componentele native și API-urile de care aveam nevoie pentru a face acest lucru. Aceste componente vor beneficia toată lumea construirea unei aplicații în viitor. Chiar dacă ar fi trebuit să construim câteva componente noi înșine, folosind React Native peste pure native încă ar fi meritat. Oricum ar fi trebuit să scriem acele componente și probabil că nu ar fi fost reutilizabile de alte echipe de pe drum.
o lecție pe care am învățat-o a fost că lucrul în depozite separate de coduri iOS și Android este dificil, chiar și cu o mulțime de instrumente și automatizare. Când am construit aplicația, Facebook a folosit acest model și toate procesele noastre de automatizare și dezvoltare au fost configurate în jurul acestuia. Cu toate acestea, nu funcționează bine pentru un produs care, în cea mai mare parte, are o singură bază de cod JavaScript partajată. Din fericire, Facebook se mută într — un depozit unificat pentru ambele platforme-va fi necesară o singură copie a codului JavaScript comun, iar sincronizările vor fi un lucru din trecut.
o altă lecție pe care am învățat-o se referea la testare. Atunci când face modificări, fiecare inginer trebuie să fie atent pentru a testa pe ambele platforme, iar procesul este predispus la erori umane. Dar aceasta este doar o consecință inevitabilă a dezvoltării unei aplicații cross-platform din aceeași bază de cod. Acestea fiind spuse, costul unui incident ocazional din cauza testării insuficiente este cu mult depășit de eficiența dezvoltării obținută prin utilizarea React Native și posibilitatea de a reutiliza codul pe ambele platforme în primul rând. Rețineți că această lecție nu se aplică numai inginerilor de produse; se aplică și inginerilor de platformă nativă React care lucrează în Objective-C și Java. O mare parte din munca pe care o fac acești ingineri nu se limitează doar la limbile materne respective. De asemenea, poate afecta JavaScript — de exemplu, API-uri componente sau implementări parțial partajate. Inginerii nativi iOS nu sunt de obicei obișnuiți să testeze modificările pe Android, iar inversul este valabil pentru inginerii Android. Acesta este în principal un decalaj cultural care a necesitat timp și efort pentru a se închide și, ca urmare, în timp, stabilitatea noastră a crescut.
de asemenea, am abordat problema construind teste de integrare care să ruleze la fiecare revizuire. În timp ce acest lucru a funcționat din cutie pentru a prinde probleme iOS pe iOS și, de asemenea, pentru Android, sistemele noastre de integrare continuă nu au fost configurate pentru a rula teste Android pe reviziile iOS și invers. Acest lucru a necesitat eforturi inginerești pentru a rezolva și există încă o marjă de eroare suficient de mare pentru a rupe ocazional aplicația.
când totul a fost spus și făcut, pariul nostru a dat roade — am reușit să livrăm prima aplicație nativă React complet a Facebook pe două platforme, cu aspect nativ, construită de aceeași echipă de ingineri JavaScript. Nu toți inginerii erau familiarizați cu React când s-au alăturat echipei, totuși au construit o aplicație iOS cu aspect nativ în doar cinci luni. Și după încă trei luni, am lansat versiunea Android a aplicației.într-un efort de a fi mai incluziv în limba noastră, am editat acest post pentru a înlocui lista neagră cu lista de blocuri.