Yan effektlər: naviqasiya, dialoq, SnackBar
State management-in ən çox səhv edilən hissəsi bir dəfə baş verməli olan işlərdir: naviqasiya, dialoq, SnackBar, analytics event-i, faylın paylaşılması.
Problem build metodunun təbiətindən doğur: o, istənilən anda təkrar çağırıla bilər (ata widget rebuild oldu, klaviatura açıldı, ekran döndü). Yan effekti build-in içinə qoysanız, o da təkrarlanır — nəticədə iki dəfə naviqasiya, üst-üstə düşən iki dialoq və ya təkrarlanan analytics event-i.
Hər library-nin bunun üçün ayrılmış yeri var:
- BLoC —
BlocListener(və yaBlocConsumer-inlistenerhissəsi),listenWhenilə süzgəc. - Riverpod —
ref.listen(buildiçində təhlükəsizdir),build-dən kənardaref.listenManual. - Provider — ayrılmış listener API-si yoxdur:
ChangeNotifier-inaddListener-iinitState/didChangeDependencies-də əl ilə qeyd olunur (vədispose-da silinir), ya da yan effekt birbaşa callback-in içində,context.readilə icra edilir.
// BLoC: listener yan effekt üçün ayrılmış yerdir.
BlocListener<LoginBloc, LoginState>(
listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) {
if (state.status == LoginStatus.success) {
Navigator.of(context).pushReplacementNamed('/home');
}
},
child: const LoginForm(),
);
// Riverpod: ref.listen build içində təhlükəsizdir.
class LoginPage extends ConsumerWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen(loginProvider, (previous, next) {
if (next.isLoggedIn && !(previous?.isLoggedIn ?? false)) {
Navigator.of(context).pushReplacementNamed('/home');
}
});
return const LoginForm();
}
}
// Provider: listener əl ilə qeyd olunur və mütləq silinir.
class _LoginPageState extends State<LoginPage> {
LoginModel? _model;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final model = context.read<LoginModel>();
if (model != _model) {
_model?.removeListener(_onModelChanged);
_model = model..addListener(_onModelChanged);
}
}
void _onModelChanged() {
if (!mounted) return; // async bildirişdən sonra qorunma
if (_model!.isLoggedIn) {
Navigator.of(context).pushReplacementNamed('/home');
}
}
@override
void dispose() {
_model?.removeListener(_onModelChanged);
super.dispose();
}
@override
Widget build(BuildContext context) => const LoginForm();
}Bir dəfə baş verməli olan naviqasiya — üç library, üç düzgün yer.
`await`-dan sonra `context`. Async əməliyyat bitdikdə widget artıq ağacda olmaya bilər. Bu halda Navigator.of(context) və ya ScaffoldMessenger.of(context) çağırmaq xətaya aparır.
Qorunma iki formadadır:
Statesinfindəmountedyoxlanışı;BuildContext-in özündə dəmountedxassəsi var.- Dart-ın
use_build_context_synchronouslylint qaydası bu səhvi statik analiz səviyyəsində tutur — komandada onu aktiv saxlamaq code review yükünü azaldır.
Bir dəfəlik effektin state-də saxlanması. "Naviqasiya et" kimi hadisə state-in bir hissəsi kimi saxlanılırsa (shouldNavigate: true), onu istifadədən sonra təmizləmək lazımdır, əks halda ekrana qayıdanda effekt təkrarlanır. Üç praktik yol:
- Effekti state-də deyil, listener-də
previous/nextmüqayisəsi ilə müəyyən etmək (ən sadə və ən çox istifadə olunan). - Bir dəfəlik sahəni istifadədən sonra sıfırlamaq (
emit(state.copyWith(navigateTo: null))). - Effektləri ayrı bir axın kimi modelləşdirmək (məsələn xəbərdarlıqların stream-i) və UI-da onu dinləmək.
| Yan effekt | Düzgün yer | Tipik səhv |
|---|---|---|
| Naviqasiya | `BlocListener` / `ref.listen` / callback | `build`-də `if (state.success) Navigator...` — iki dəfə keçid |
| SnackBar / dialoq | Listener; `listenWhen` ilə yalnız yeni xətada | Hər rebuild-də təkrar göstərmək |
| Analytics event-i | Listener ya da notifier/bloc-un öz metodu | `build`-də göndərmək → şişirdilmiş statistika |
| `await`-dan sonra `context` istifadəsi | `mounted` yoxlanışı; `use_build_context_synchronously` lint-i | Yoxlamadan istifadə → söküləmiş widget xətası |
Riverpod sənədi bu mövzuya ayrıca qayda ilə toxunur: provider-in inisiallaşdırılması zamanı yan effekt icra etməyin — provider oxunuş əməliyyatını təmsil edir; form göndərmək kimi "yazma" əməliyyatları üçün provider istifadə etmək düzgün deyil (sənəd təcrübi Mutation mexanizmini alternativ kimi göstərir). Praktik nəticə: yan effektləri provider-in gövdəsində deyil, notifier-in metodlarında və UI listener-lərində saxlamaq.
📚 Mənbələr və sənədlər
- use_build_context_synchronously lint qaydasırəsmidart.dev
await-dan sonra context istifadəsinin niyə problemli olduğunun rəsmi izahı.
- BuildContext.mountedrəsmiapi.flutter.dev
- Riverpod: DO / DON'Trəsmiriverpod.dev
Provider-in inisiallaşdırılmasında yan effekt qadağası.
- Flutter Bloc: BlocListenerrəsmibloclibrary.dev
BlocListener və listenWhen-in rəsmi təsviri.