Sparround

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:

  • BLoCBlocListener (və ya BlocConsumer-in listener hissəsi), listenWhen ilə süzgəc.
  • Riverpodref.listen (build içində təhlükəsizdir), build-dən kənarda ref.listenManual.
  • Provider — ayrılmış listener API-si yoxdur: ChangeNotifier-in addListener-i initState/didChangeDependencies-də əl ilə qeyd olunur (və dispose-da silinir), ya da yan effekt birbaşa callback-in içində, context.read ilə icra edilir.
dart
// 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:

  • State sinfində mounted yoxlanışı; BuildContext-in özündə də mounted xassəsi var.
  • Dart-ın use_build_context_synchronously lint 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/next mü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 effektDüzgün yerTipik səhv
Naviqasiya`BlocListener` / `ref.listen` / callback`build`-də `if (state.success) Navigator...` — iki dəfə keçid
SnackBar / dialoqListener; `listenWhen` ilə yalnız yeni xətadaHər rebuild-də təkrar göstərmək
Analytics event-iListener 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-iYoxlamadan 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