Správa stavu vo Flutteri bez zbytočných rečí

David Havlin
David Havlin
Dizajn a vývoj
Biely 3D panel aplikácie s modulárnymi dlaždicami stavu na tmavom pozadí.

Flutter vám dáva priveľa možností. A to je problém.

Správa stavu je vo Flutteri jedna z tém, na ktoré má názor každý. Diskusia sa rýchlo zmení na súboj „toto je správna cesta“ proti „toto je zastarané“.

Práve tu sa to začína kaziť. Na reaktívny stav netreba milión vrstiev, zbytočnú zložitosť a opakovaný kód. Dá sa to urobiť jednoducho, čisto a tak, aby bol kód čitateľný aj o pol roka.

Toto je osobný pohľad založený na používaní signals v produkcii a čase strávenom hlbším skúmaním Riverpodu. Nie univerzálne pravidlo. Len náš súčasný postoj.

setState. Provider. Bloc. Cubit. Riverpod. Signals. Zoznam sa stále predlžuje.

Výber je dobrá vec. Má však vedľajší účinok: vývojári, najmä tí novší vo Flutteri, začnú veriť, že zložitejšie riešenie je automaticky lepšie. Ak nepoužívate módny nástroj, asi to robíte zle.

Tento predpoklad stojí tímy skutočný čas a peniaze.

Riverpod je moderný štandard. To neznamená, že sa hodí vždy.

Riverpod je dobrý nástroj. Dáva správe stavu jasnú štruktúru, ponúka opakovane použiteľné komponenty, cache aj vývojárske nástroje. Mnohí vývojári vo Flutteri po ňom siahajú automaticky a má to svoje dôvody.

Zložitosť je však problém, keď jednoduché veci prestanú byť jednoduché.

Pojmy Consumer, Notifier a všetky varianty providerov nepôsobia intuitívne, najmä ak prichádzate z JavaScriptu alebo webu. Na rovinu: pri malých a stredných aplikáciách sa mi vzhľadom na prínos zdá Riverpod mätúci a plný opakovaného kódu. Samotná funkcia môže byť jednoduchá, ale nastavenie stavu už pôsobí ťažkopádnejšie než problém, ktorý rieši.

Dokumentácia je dôkladná, no pri učení základov jej obrovský rozsah skôr prekáža, než pomáha.

Jednoduchší prístup, ktorý funguje v produkcii

V našich projektoch používame túto kombináciu nástrojov na jednoduchú a prehľadnú správu stavu:

  • signals na reaktivitu
  • extended signals na trvalé uchovávanie stavu
  • getIt na vkladanie závislostí
  • injectable na prehľadné zapojenie závislostí
  • triedy store na štruktúru

Základný model je priamočiary. signal(...) je stav. computed(...) je odvodený stav. Watch(...) je reaktívna časť UI. To je celý princíp.

Nad tým používame triedy store. Store drží stav, odvodené hodnoty a metódy spolu. Je to obyčajná singleton trieda. Práve v tom je pointa.

Skutočné príklady z nášho kódu

1. Store pre mapu

Tu je store, ktorý spravuje stav mapy. Meniteľný stav, odvodený stav aj metódy na jednom mieste.

@singleton
class MapStore {
 final searchQuery = signal('');
 final mapViewType = signal(MapViewType.simple);

 late final isSimpleMap =
     computed(() => mapViewType.value == MapViewType.simple);

 void updateSearch(String query) {
   searchQuery.value = query;
 }
}

Bez providerov, notifierov a prepisovania metód build. Stav je signal. Odvodený stav je computed. Metódy menia signal priamo.

2. Presné prekresľovanie UI

Toto prináša najväčší praktický rozdiel. Môžete mať veľkú metódu build, obaliť pomocou Watch len malú reaktívnu časť a pri zmene signálu sa prekreslí iba daný widget.

Row(
 children: [
   const Text('Offline: '),
   Watch((ctx) => Text(store.isOffline.value ? 'Yes' : 'No')),
 ],
)

Bez widgetu Consumer. Bez ref.watch so selektormi. Stačí obaliť reaktívnu časť. Hotovo.

Signals a Riverpod vedľa seba

Nejde o to, že jeden je objektívne lepší. Ide o to, čo sa vo väčšine projektov ľahšie číta, učí a udržiava.

Základný stav

Signals:

final count = signal(0);
count.value++;

Riverpod:

final counterProvider =
   NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

class CounterNotifier extends Notifier<int> {
 @override
 int build() => 0;

 void increment() => state++;
}

Obe verzie robia to isté. So signals stačia dva riadky. Riverpod potrebuje triedu, deklaráciu providera, metódu build a metódu na zmenu stavu.

Odvodený stav

Signals:

final count = signal(0);
final doubled = computed(() => count.value * 2);

Riverpod:

final counterProvider =
   NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

final doubledProvider = Provider((ref) {
 final count = ref.watch(counterProvider);
 return count * 2;
});

Pri Riverpode už treba držať v hlave viac vecí. Samostatný provider pre odvodený stav, volanie ref.watch, vzťah medzi oboma providermi. Pri signals je závislosť implicitná v callbacku computed.

Reaktivita UI

Signals:

Watch((context) => Text('${store.likes.value}'))

Riverpod:

Consumer(
 builder: (context, ref, _) {
   final likes = ref.watch(shopProvider.select((s) => s.likes));
   return Text('$likes');
 },
)

Pri aktualizácii jednotlivých častí rozhrania je rozdiel najviditeľnejší. V Riverpode potrebujete Consumer, ref, watch a select. Pri signals obalíte widget pomocou Watch a prečítate hodnotu. Jeden koncept namiesto štyroch.

Kedy použiť čo

Praktická otázka neznie „ktorý nástroj je najpokročilejší?“, ale „s ktorým zostane aplikácia zrozumiteľná aj o pol roka?“.

Ak signals riešia problém prehľadne, zostaňte pri nich. Ak potrebujete trvalé ukladanie, vkladanie závislostí či ďalšiu štruktúru, pridajte ich priamo namiesto výmeny celého modelu stavu. Ak tím Riverpod dobre pozná a využíva jeho model, dáva zmysel Riverpod. Záleží na kontexte.

Zmysel nedáva predpoklad, že viac abstrakcie automaticky znamená lepšiu architektúru.

Ani signals nie sú dokonalé

Signals nevyriešia každý problém architektúry. Stále musíte premyslieť štruktúru kódu, názvy a rozdelenie zodpovedností. Nemajú zabudovanú cache ani vývojárske nástroje a tímu nepredpisujú konkrétny návrhový vzor.

Základný princíp signals je však taký jednoduchý, že ho nový vývojár pochopí za hodinu, nie za týždeň. Vo väčšine projektov sa nám tento kompromis oplatí.

Ak prichádzate z Vue alebo Svelte, signals vám budú okamžite známe. Reaktívne prvky sú takmer totožné. Pri prechode z Reactu je zmena myslenia menšia než učenie Riverpodu od nuly.

Čo si z toho vziať

Na seriózny produkt nepotrebujete vždy ďalšie vrstvy a viac opakovaného kódu.

Signals sa ľahšie učia, čítajú aj používajú v skutočnom kóde. Riverpod je veľmi dobrý nástroj, ktorý rieši veľa problémov, a jeho popularita medzi Flutter vývojármi má dôvod. Pre bežnú prácu väčšiny tímov, najmä na malých a stredných produktoch, však často lepšie sedí jednoduchší reaktívny model.

Vyberte nástroj, s ktorým sa kód ľahšie odovzdáva, nie ten, ktorý vyzerá najpôsobivejšie na konferenčnej prednáške.

Zdieľať na

Spýtajte sa Outloud

Čo práve
riešite?

Preberme váš projekt, problém vo firme alebo niečo, čo by ste si radi uľahčili.

Odpovede pripravuje AI od OpenAI. Rozhovory môžeme poslať nášmu tímu e-mailom na kontrolu. Ochrana súkromia