Ghidul dezvoltatorului Android pentru modelul de navigare fragmentat

Publicat: 2022-03-11

De-a lungul anilor, am văzut multe implementări diferite de modele de navigare în Android. Unele dintre aplicații foloseau numai Activități, în timp ce altele Activități combinate cu Fragmente și/sau cu Vizualizări personalizate.

Una dintre implementările mele preferate de model de navigare se bazează pe filozofia „One-Activity-Multiple-Fragments” sau pur și simplu Fragment Navigation Pattern, în care fiecare ecran din aplicație este un fragment de ecran complet și toate sau majoritatea acestor fragmente sunt conținute în o activitate.

Această abordare nu numai că simplifică modul în care este implementată navigarea, dar are performanțe mult mai bune și, în consecință, oferă o experiență mai bună pentru utilizator.

În acest articol ne vom uita la câteva implementări comune a modelelor de navigare în Android, apoi vom introduce modelul de navigare bazat pe fragmente, comparând și contrastând cu celelalte. O aplicație demonstrativă care implementează acest model a fost încărcată pe GitHub.

Lumea activităților

O aplicație Android tipică care utilizează numai activități este organizată într-o structură arborescentă (mai precis într-un grafic direcționat) în care activitatea rădăcină este pornită de lansator. Pe măsură ce navigați în aplicație, există o stivă de activitate menținută de sistemul de operare.

Un exemplu simplu este prezentat în diagrama de mai jos:

Fragment de imagine 1

Activitatea A1 este punctul de intrare în aplicația noastră (de exemplu, reprezintă un ecran de splash sau un meniu principal) și din acesta utilizatorul poate naviga la A2 sau A3. Când trebuie să comunicați între activități, puteți utiliza startActivityForResult() sau poate partajați un obiect logic de afaceri accesibil la nivel global între ele.

Când trebuie să adăugați o activitate nouă, trebuie să efectuați următorii pași:

  • Definiți noua activitate
  • Înregistrați-l în AndroidManifest.xml
  • Deschideți-l cu startActivity() dintr-o altă activitate

Desigur, această diagramă de navigare este o abordare destul de simplistă. Poate deveni foarte complex atunci când trebuie să manipulați stiva din spate sau când trebuie să refolosiți aceeași activitate de mai multe ori, de exemplu când doriți să navigați utilizatorul prin anumite ecrane de tutorial, dar fiecare ecran folosește de fapt aceeași activitate ca un baza.

Din fericire, avem instrumente pentru aceasta numite sarcini și câteva linii directoare pentru o navigare corectă în back stack.

Apoi, cu nivelul API 11 au venit fragmente...

Lumea fragmentelor

Android a introdus fragmente în Android 3.0 (nivelul API 11), în primul rând pentru a suporta design-uri de UI mai dinamice și mai flexibile pe ecrane mari, cum ar fi tabletele. Deoarece ecranul unei tablete este mult mai mare decât cel al unui telefon, există mai mult spațiu pentru a combina și a schimba componentele UI. Fragmentele permit astfel de modele fără a fi nevoie să gestionați modificări complexe ale ierarhiei vizualizărilor. Prin împărțirea aspectului unei activități în fragmente, puteți modifica aspectul activității în timpul execuției și puteți păstra acele modificări într-o stivă din spate care este gestionată de activitate. – citat din ghidul Google API pentru Fragments.

Această nouă jucărie a permis dezvoltatorilor să construiască o interfață de utilizare cu mai multe panouri și să refolosească componentele în alte activități. Unii dezvoltatori iubesc acest lucru, în timp ce alții nu. Este o dezbatere populară dacă să folosiți fragmente sau nu, dar cred că toată lumea ar fi de acord că fragmentele au adus o complexitate suplimentară și dezvoltatorii chiar trebuie să le înțeleagă pentru a le folosi corect.

Ecran complet Fragment Nightmare în Android

Am început să văd tot mai multe exemple în care fragmentele nu reprezentau doar o parte a ecranului, ci de fapt întregul ecran era un fragment conținut într-o activitate. Odată am văzut chiar și un design în care fiecare activitate avea exact un fragment de ecran complet și nimic mai mult și singurul motiv pentru care existau aceste activități a fost găzduirea acestor fragmente. Pe lângă defectul evident de design, există o altă problemă cu această abordare. Aruncă o privire la diagrama de mai jos:

Fragment de imagine 2

Cum poate comunica A1 cu F1? Ei bine, A1 are control total asupra F1 de când a creat F1. A1 poate trece un pachet, de exemplu, la crearea lui F1 sau poate invoca metodele sale publice. Cum poate comunica F1 cu A1? Ei bine, acest lucru este mai complicat, dar poate fi rezolvat cu un model de apel invers/observator în care A1 se abonează la F1 și F1 îl notifică pe A1.

Dar cum pot A1 și A2 să comunice între ei? Acest lucru a fost deja acoperit, de exemplu prin startActivityForResult() .

Și acum vine adevărata întrebare: cum pot F1 și F2 să comunice între ele? Chiar și în acest caz putem avea o componentă de logică de afaceri care este disponibilă la nivel global, deci poate fi folosită pentru a transmite date. Dar acest lucru nu duce întotdeauna la un design elegant. Ce se întâmplă dacă F2 trebuie să transmită niște date către F1 într-un mod mai direct? Ei bine, cu un model de apel invers F2 poate notifica A2, apoi A2 termină cu un rezultat și acest rezultat este capturat de A1 care îl anunță pe F1.

Această abordare necesită mult cod standard și devine rapid o sursă de bug-uri, durere și furie.

Dacă am putea scăpa de toate activitățile și am putea păstra doar una dintre ele, care păstrează restul fragmentelor?

Model de navigare fragment

De-a lungul anilor, am început să folosesc modelul „One-Activity-Multiple-Fragments” în majoritatea aplicațiilor mele și încă îl folosesc. Există o mulțime de discuții despre această abordare, de exemplu aici și aici. Ceea ce am ratat însă este un exemplu concret pe care îl pot vedea și testa.

Să aruncăm o privire la următoarea diagramă:

Imaginea Framgnet 3

Acum avem o singură activitate container și avem mai multe fragmente care au din nou o structură de tip arbore. Navigarea între ele este gestionată de FragmentManager , are stiva sa din spate.

Observați că acum nu avem startActivityForResult() dar putem implementa un model de apel invers/observator. Să vedem câteva avantaje și dezavantaje ale acestei abordări:

Pro:

1. AndroidManifest.xml mai curat și mai ușor de întreținut

Acum că avem o singură activitate, nu mai trebuie să actualizăm manifestul de fiecare dată când adăugăm un nou ecran. Spre deosebire de activități, nu trebuie să declarăm fragmente.

Acest lucru ar putea părea un lucru minor, dar pentru aplicațiile mai mari care au peste 50 de activități, acest lucru poate îmbunătăți semnificativ lizibilitatea fișierului AndroidManifest.xml .

Uitați-vă la fișierul manifest al aplicației exemplu care are mai multe ecrane. Fișierul manifest rămâne în continuare super simplu.

 <?xml version="1.0" encoding="utf-8"?> package="com.exarlabs.android.fragmentnavigationdemo.ui" > <application android:name= ".FragmentNavigationDemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

2. Management centralizat de navigare

În exemplul meu de cod, veți vedea că folosesc NavigationManager , care în cazul meu este injectat în fiecare fragment. Acest manager poate fi folosit ca un loc centralizat pentru înregistrare, gestionarea stivei din spate și așa mai departe, astfel încât comportamentele de navigare sunt decuplate de restul logicii de afaceri și nu sunt răspândite în implementările diferitelor ecrane.

Să ne imaginăm o situație în care am dori să pornim un ecran în care utilizatorul poate selecta unele elemente dintr-o listă de persoane. De asemenea, ați dori să treceți câteva argumente de filtrare precum vârsta și ocupația și sexul.

În cazul Activităților, veți scrie:

 Intent intent = new Intent(); intent.putExtra("age", 40); intent.putExtra("occupation", "developer"); intent.putExtra("gender", "female"); startActivityForResult(intent, 100);

Apoi trebuie să definiți onActivityResult undeva mai jos și să gestionați rezultatul.

 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); }

Problema mea personală cu această abordare este că aceste argumente sunt „extra” și nu sunt obligatorii, așa că trebuie să mă asigur că activitatea de primire se ocupă de toate cazurile diferite în care lipsește un extra. Mai târziu, când se face ceva refactorizare și nu mai este nevoie de „vârsta” de exemplu, atunci trebuie să caut peste tot în codul de unde încep această activitate și să mă asigur că toate extrasele sunt corecte.

Mai mult, nu ar fi mai frumos dacă rezultatul (lista de persoane) ar ajunge sub formă de _List _ și nu într-o formă serializată care trebuie deserializată?

În cazul navigării bazate pe fragmente, totul este mai simplu. Tot ce trebuie să faceți este să scrieți o metodă în NavigationManager numită startPersonSelectorFragment() cu argumentele necesare și cu o implementare callback.

 mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() { @Override public boolean onPersonsSelected(List<Person> selection) { [do something] return false; } });

Sau cu RetroLambda

 mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);

3. Mijloace mai bune de comunicare între ecrane

Între activități, putem partaja doar un Bundle care poate conține date primitive sau seriate. Acum, cu fragmente, putem implementa un model de apel invers în care, de exemplu, F1 poate asculta F2 care trece obiecte arbitrare. Vă rugăm să aruncați o privire la implementarea callback-ului din exemplele anterioare, care returnează o _List _.

4. Fragmentele de construcție sunt mai puțin costisitoare decât activitățile de construcție

Acest lucru devine evident atunci când utilizați un sertar care are de exemplu 5 elemente de meniu și pe fiecare pagină sertarul ar trebui să fie afișat din nou.

În cazul navigării cu activitate pură, fiecare pagină ar trebui să umfle și să inițializeze sertarul, ceea ce este, desigur, costisitor.

Pe diagrama de mai jos puteți vedea mai multe fragmente rădăcină (FR*) care sunt fragmente de ecran complet care pot fi accesate direct din sertar și, de asemenea, sertarul este accesibil doar atunci când aceste fragmente sunt afișate. Tot ceea ce se află în dreapta liniei întrerupte în diagramă este acolo ca exemplu de schemă de navigare arbitrară.

Imaginea Framgnet 4

Deoarece activitatea containerului deține sertarul, avem o singură instanță de sertar, așa că la fiecare pas de navigare în care sertarul ar trebui să fie vizibil, nu trebuie să-l umflați și să-l inițializați din nou. Încă nu sunteți convins cum funcționează toate acestea? Aruncă o privire la aplicația mea exemplu care demonstrează utilizarea sertarelor.

Contra

Cea mai mare teamă a mea a fost întotdeauna că, dacă aș folosi un model de navigare bazat pe fragmente într-un proiect, undeva, pe drum, aș întâlni o problemă neprevăzută, care ar fi dificil de rezolvat în jurul complexității adăugate a fragmentelor, a bibliotecilor terțe și a diferitelor versiuni ale sistemului de operare. Dacă ar trebui să refactorizez tot ce am făcut până acum?

Într-adevăr, a trebuit să rezolv probleme cu fragmente imbricate, biblioteci terță parte care folosesc și fragmente precum ShinobiControls, ViewPagers și FragmentStatePagerAdapters.

Trebuie să recunosc că a dobândi suficientă experiență cu fragmentele pentru a putea rezolva aceste probleme a fost un proces destul de lung. Dar, în fiecare caz, problema nu a fost că filosofia este rea, ci că nu am înțeles destul de bine fragmentele. Poate dacă înțelegeți fragmentele mai bine decât mine, nici măcar nu ați întâlni aceste probleme.

Singurul dezavantaj pe care îl pot menționa acum este că încă putem întâmpina probleme care nu ar fi trivial de rezolvat, deoarece nu există nicio bibliotecă matură care să prezinte toate scenariile complexe ale unei aplicații complexe cu navigare bazată pe fragmente.

Concluzie

În acest articol, am văzut o modalitate alternativă de a implementa navigarea într-o aplicație Android. Am comparat-o cu filozofia tradițională de navigație care utilizează activități și am văzut câteva motive bune pentru care este avantajos să o folosim față de abordarea tradițională.

În cazul în care nu ați făcut-o deja, consultați aplicația demo încărcată în implementarea GitHub. Simțiți-vă liber să bifurcați sau să contribuiți la el cu exemple mai frumoase care ar arăta mai bine utilizarea acestuia.

Înrudit: Top 10 cele mai frecvente greșeli pe care le fac dezvoltatorii Android