# Flutter klient, který funguje v reálném světě: tři řešení pro složité scénáře
Teoreticky vypadá architektura Flutter aplikace ideálně: Bloc pro stav, Dio pro API, go_router pro navigaci. V praxi se ale reálné projekty potýkají s problémy mimo učební příklady – od změny uživatele po pozadíové operace. Jak postavit klienta, který se nerozpadne při prvním odchylu od happy path?
Probereme případ plnohodnotné aplikace pro studium jazyků s množstvím složitých funkcí: multiprovajderová autentizace, anonymní přihlášení, překlad, rozpoznávání řeči, generování obsahu, offline režim a WebSocket spojení. Standardní balíčky (flutter_bloc, dio, get_it) řeší jednotlivé úkoly, ale ne pokryjí scénáře, kde produktová logika přesahuje rámec jednoduchých příkladů. Hlavní problémy vznikají při:
- Přiřazení anonymního uživatele k trvalému účtu bez ztráty dat
- Práci při formálně dostupné, ale nestabilní síti
- Zpracování dlouhých operací (desítky sekund) bez blokování UI
- Korektní změně identity při logout/login
- Zaručeném vyčištění stavu po odhlášení uživatele
Tyto případy vyžadují ne výběr nových balíčků, ale fundamentální řešení v architektuře. Zde jsou tři přístupy, které pomohly vytvořit odolného klienta.
Oddělení identity a aplikačního přístupu
Standardní cesta – použít Firebase Auth jako jediný zdroj pravdy. Při složité business logice to ale vede k problémům. Například při přechodu z anonymního přihlášení na registraci přes Google se ztrácejí data a serverová pravidla přístupu se tuž zavážou k Firebase.
Rozdělili jsme odpovědnost:
- Firebase odpovídá za identity (email/password, Google, Apple, anonymní přihlášení)
- Backend generuje vlastní JWT token po ověření identity
Schéma práce:
- Uživatel se přihlásí přes Firebase
- Klient obdrží Firebase ID token
- Pošle ho na backend
- Backend vrátí aplikační JWT
- Všechny následné požadavky (REST i WebSocket) používají tento JWT
Tento přístup řeší tři klíčové problémy:
- Nezávislost na provajderu: výměna Firebase za jiný identity provider neovlivní backend logiku
- Unifikace transportu: jeden token funguje pro HTTP, WebSocket i správu práv
- Plynulá vazba účtů: anonymní uživatel si zachová data při přechodu na trvalý účet
Kritická chyba zde – pokus použít Firebase token přímo pro přístup k API. Backend musí spravovat svá práva a životní cyklus relace. Dvuetapová autentizace přidává složitost, ale vyplatí se flexibilitou a shodou s reálnými scénáři.
Kompletní reset kontextu místo manuálního čištění
Při pokusu logicky čistit stav po logoutu (nulování TokenCubit, UserCubit, cache) nevyhnutelně vznikají úniky. Zejména ve scénářích:
- Změna uživatele (logout → login pod jiným účtem)
- Vazba anonymního profilu k registrovanému
- Obnovení relace po expirovaném tokenu
- Návrat z hluboce vnořeného obrazovky
Místo bodového čištění přes vytváříme celý provajder kontext znovu. Technická realizace:
- V kořenovém widgetu AppInitializer se vytváří unikátní klíč
- Při kritické změně auth stavu (logout, změna uživatele) se klíč mění
- Subtree s MultiBlocProvider se přestaví znovu
- Všechny Cubit-y se inicializují přes GetIt z čistého listu
final GlobalKey<_AppInitializerState> appContextKey =
GlobalKey<_AppInitializerState>();
class _AppInitializerState extends State<AppInitializer> {
Key _appKey = UniqueKey();
void resetApp() {
setState(() {
_appKey = UniqueKey();
});
}
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
key: _appKey,
providers: ProvidersManager.getAllProviders(),
child: widget.child,
);
}
}
Tato metoda není elegantní, ale předvídatelná. Po resetu se aplikace chová jako nová instance relace. Důležité:
- Přesně oddělovat dlouhodobě žijící služby (např. WebSocket spojení) od stavů obrazovek
- Nastavit DI tak, aby singletony nebyly ovlivněny při přestavbě
- Netočit tento přístup pro jednoduché aplikace (méně než 5 obrazovek)
Pro složité projekty je kompletní reset kontextu levnější a spolehlivější než nekonečné manuální sanitární čištění.
Hybridní HTTP + WebSocket pro dlouhé operace
Standardní pattern final response = await dio.post(...) selhává u operací trvajících desítky sekund (analýza textu, generování obsahu, rozpoznávání). Problémy:
- Timeouty HTTP požadavků
- Nestabilita při minimalizaci aplikace
- Špatný UX při dlouhém čekání
- Složitá diagnostika chyb
Řešení – přechod na hybridní model:
- Klient pošle požadavek s hlavičkou
X-Async-Background: true - Server vrátí
task_idmísto finálního výsledku - Klient se přihlásí k události dokončení přes WebSocket
- Po notifikaci stáhne výsledek samostatným REST požadavkem
Klíčové výhody:
- Evolučnost: rychlý endpoint se může stát pozadíovým bez přepsání klientské logiky
- Spolehlivost: WebSocket odpovídá za signál dokončení, REST za získání výsledku
- Diagnostika: oddělené body pro traceování, retry a zpracování chyb
Pro mobilního klienta je kritické realizovat:
- Automatický reconnect
- Heartbeat mechanismus
- Frontu zpráv
- Zpracování lifecycle událostí aplikace
- Oddělené streamy pro statusy, data a chyby
To mění WebSocket z „kanálu notifikací“ na systémovou vrstvu infrastruktury. Hlavička X-Async-Background je zde rozhraním k hotovému backend řešení, ne kličkou klienta.
Co je důležité: klíčové závěry
- Oddělujte identity a access: Firebase/Apple/Google mají ověřovat identitu, ale nespravovat práva přístupu k zdrojům
- Resetujte kontext, ne čistěte stav: při změně uživatele přestavte provajder strom místo manuálního čištění
- Navrhujte s ohledem na evoluci: dlouhé operace stavte hned podle schématu HTTP (start) → WebSocket (signál) → HTTP (výsledek)
- Testujte scénáře mimo happy path: anonymní přihlášení → registrace, síť s vysokým pingem, změna uživatele v hlubokém navigačním staku
- Izolujte infrastrukturní vrstvy: WebSocket musí být součástí systému, ne výjimkou
Tato řešení nevznikla z teoretických preferencí, ale po reálných selháních. Flutter poskytuje nástroje, ale odolnost aplikace závisí na tom, jak zpracováváte hranice mezi komponentami. Soustředění se na scénáře, kde vše jde krkem, je jediný způsob, jak vytvořit production-ready klienta.
— Editorial Team
Zatím žádné komentáře.