Skip to content

Bab 23 — Akses BLoC & Navigasi

Menyediakan sebuah BLoC untuk satu layar itu mudah. Yang sering menjebak adalah mempertahankan akses ke instance yang sama ketika pengguna berpindah antar layar — karena Navigator bekerja dengan cara yang tidak selalu intuitif.

Bab ini membahas tiga cakupan akses, dan mengapa satu kesalahan kecil bisa membuat penghitungmu tiba-tiba kembali ke nol.

Kenapa navigasi menyulitkan

Ingat dari Bab 21 bahwa context.read<T>() menelusuri element tree ke atas mencari BlocProvider<T> terdekat.

Masalahnya: ketika kamu memanggil Navigator.push, layar baru tidak ditempatkan sebagai anak dari layar sebelumnya. Ia didorong sebagai rute baru yang bercabang langsung dari Navigator — yang berada di dalam MaterialApp.

   MaterialApp

   Navigator  ◄─────────── semua rute bercabang dari sini

       ├── Route 1: HomeScreen
       │       │
       │   BlocProvider<CounterCubit>   ← disediakan DI SINI
       │       │
       │   Isi HomeScreen

       └── Route 2: SecondScreen   ← didorong dengan push()

           Isi SecondScreen

           context.read<CounterCubit>()  💥 TIDAK DITEMUKAN

SecondScreen bukan keturunan dari BlocProvider itu. Pencarian ke atas melewati Navigator, lalu MaterialApp, dan tidak menemukan apa pun.

Errornya:

Error: Could not find the correct Provider<CounterCubit> above this
SecondScreen Widget.

Ada tiga cakupan akses yang menyelesaikan ini, masing-masing untuk situasi yang berbeda.

1. Akses lokal

Cakupan paling sempit: BLoC hanya hidup selama satu layar terbuka, dan dibuang ketika layar ditutup.

dart
class TodoScreen extends StatelessWidget {
  const TodoScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider<TodoCubit>(
      create: (_) => TodoCubit(),
      child: const _IsiTodoScreen(),
    );
  }
}

class _IsiTodoScreen extends StatelessWidget {
  const _IsiTodoScreen();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Tugas')),
      body: BlocBuilder<TodoCubit, TodoState>(
        builder: (context, state) => DaftarTugas(state: state),
      ),
    );
  }
}

Ini pilihan yang tepat untuk state yang memang hanya relevan di satu layar: formulir, hasil pencarian, keranjang sementara.

Keuntungannya: memori dibersihkan otomatis ketika layar ditutup, dan setiap kali layar dibuka kamu mendapat state yang bersih.

Perhatikan pemisahan menjadi _IsiTodoScreen. Ini bukan sekadar kerapian — ini solusi untuk masalah context yang dibahas di Bab 21: widget di dalam BlocProvider butuh context-nya sendiri.

2. Akses rute

Kamu punya BLoC di layar A, dan ingin layar B memakai instance yang sama. Untuk ini ada BlocProvider.value.

dart
// Di HomeScreen
ElevatedButton(
  onPressed: () {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (ctxBaru) => BlocProvider.value(
          // Ambil instance yang SUDAH ADA, jangan buat baru
          value: context.read<CounterCubit>(),
          child: const SecondScreen(),
        ),
      ),
    );
  },
  child: const Text('Buka Layar Kedua'),
)

Perhatikan dengan teliti: context.read<CounterCubit>() dipanggil memakai context milik HomeScreen — yang berada di bawah BlocProvider. Sementara ctxBaru di dalam builder adalah context rute baru yang tidak punya akses.

   Navigator
       ├── Route 1: HomeScreen
       │       │
       │   BlocProvider<CounterCubit>  ─── instance A
       │       │
       │   Tombol ──── context.read() ✅

       └── Route 2

           BlocProvider.value(value: instance A)  ← instance yang SAMA

           SecondScreen ──── context.read() ✅

Jangan pakai create untuk instance yang sudah ada

dart
// ❌ Membuat instance BARU — state layar A dan B terpisah
MaterialPageRoute(
  builder: (_) => BlocProvider<CounterCubit>(
    create: (_) => CounterCubit(),   // 💥 instance baru, state kembali 0
    child: const SecondScreen(),
  ),
)

// ✅ Memakai instance yang sudah ada
MaterialPageRoute(
  builder: (_) => BlocProvider.value(
    value: context.read<CounterCubit>(),
    child: const SecondScreen(),
  ),
)

Ini bug yang sangat membingungkan ketika terjadi: penghitungmu menunjukkan 5 di layar pertama, tetapi 0 di layar kedua — dan tidak ada error apa pun.

Perbedaan penting lainnya: BlocProvider.value tidak memanggil close(). Ia tahu bahwa instance itu dimiliki orang lain, jadi ia tidak berhak membuangnya. BlocProvider dengan create yang memilikinya, dan ia yang akan menutupnya.

3. Akses global

Untuk BLoC yang dibutuhkan di mana-mana — autentikasi, tema, status koneksi — sediakan di atas MaterialApp.

dart
// lib/main.dart
void main() {
  runApp(const AplikasiSaya());
}

class AplikasiSaya extends StatelessWidget {
  const AplikasiSaya({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<CounterCubit>(create: (_) => CounterCubit()),
        BlocProvider<InternetCubit>(
          create: (_) => InternetCubit(connectivity: Connectivity()),
        ),
        BlocProvider<SettingsCubit>(create: (_) => SettingsCubit()),
      ],
      // MaterialApp berada DI BAWAH provider,
      // sehingga Navigator dan semua rutenya ikut tercakup
      child: MaterialApp(
        title: 'Aplikasi',
        debugShowCheckedModeBanner: false,
        home: const HomeScreen(),
      ),
    );
  }
}

Sekarang pohonnya menjadi:

   MultiBlocProvider  ◄────────── di sini semua BLoC hidup

   MaterialApp

   Navigator
       ├── Route 1: HomeScreen     ──► context.read() ✅
       ├── Route 2: SecondScreen   ──► context.read() ✅
       └── Route 3: ThirdScreen    ──► context.read() ✅

Karena Navigator berada di bawah provider, setiap rute yang didorongnya otomatis punya akses.

Kapan global, kapan tidak

Menyediakan segalanya secara global itu menggoda karena "pasti berfungsi". Tetapi itu berarti:

  • Setiap BLoC hidup selama aplikasi berjalan, memakan memori.
  • State tidak pernah bersih — pengguna keluar akun, tetapi data lama masih ada.
  • Sulit melacak siapa memakai apa.

Aturan praktis: global hanya untuk yang benar-benar lintas aplikasi — auth, tema, koneksi, keranjang belanja. Sisanya lokal atau per rute.

Named routes dengan BLoC

Untuk aplikasi dengan banyak layar, memusatkan definisi rute membuat pengelolaan BLoC jauh lebih rapi.

dart
// lib/presentation/router/app_router.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../screens/home_screen.dart';
import '../screens/second_screen.dart';
import '../screens/third_screen.dart';
import '../../logic/cubit/counter_cubit.dart';

class AppRouter {
  Route? bangunRute(RouteSettings settings) {
    return switch (settings.name) {
      '/' => MaterialPageRoute(
          builder: (_) => const HomeScreen(
            title: 'Beranda',
            warna: Colors.blueAccent,
          ),
        ),
      '/second' => MaterialPageRoute(
          builder: (_) => const SecondScreen(
            title: 'Layar Kedua',
            warna: Colors.redAccent,
          ),
        ),
      '/third' => MaterialPageRoute(
          builder: (_) => const ThirdScreen(
            title: 'Layar Ketiga',
            warna: Colors.greenAccent,
          ),
        ),
      _ => null,
    };
  }
}
dart
// lib/main.dart
class AplikasiSaya extends StatefulWidget {
  const AplikasiSaya({super.key});

  @override
  State<AplikasiSaya> createState() => _AplikasiSayaState();
}

class _AplikasiSayaState extends State<AplikasiSaya> {
  // Router dibuat SEKALI, bukan di dalam build()
  final _router = AppRouter();

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<CounterCubit>(create: (_) => CounterCubit()),
        BlocProvider<InternetCubit>(
          create: (_) => InternetCubit(connectivity: Connectivity()),
        ),
      ],
      child: MaterialApp(
        title: 'Aplikasi BLoC',
        debugShowCheckedModeBanner: false,
        onGenerateRoute: _router.bangunRute,
        initialRoute: '/',
      ),
    );
  }
}

Karena BLoC disediakan secara global, setiap layar bisa memakai Navigator.pushNamed tanpa memikirkan BlocProvider.value sama sekali:

dart
ElevatedButton(
  onPressed: () => Navigator.of(context).pushNamed('/second'),
  child: const Text('Ke Layar Kedua'),
)

Router harus dibuat sekali

Perhatikan bahwa _router adalah properti final di kelas State, bukan dibuat di dalam build(). Membuatnya ulang setiap build berarti objek baru setiap kali — sama seperti masalah GlobalKey di Bab 6.

Pertanyaan yang sering muncul: bolehkah BLoC melakukan navigasi sendiri?

Jawabannya tidak. Navigasi adalah urusan presentation. BLoC yang memanggil Navigator akan butuh BuildContext, yang berarti ia harus mengimpor Flutter — melanggar aturan di Bab 22.

Cara yang benar: BLoC mengeluarkan state, dan BlocListener yang bereaksi.

dart
// ❌ SALAH — BLoC tidak boleh tahu tentang navigasi
class AuthCubit extends Cubit<AuthState> {
  Future<void> masuk(BuildContext context, String email, String sandi) async {
    // ...
    Navigator.of(context).pushReplacement(...);   // 💥
  }
}
dart
// ✅ BENAR — BLoC hanya mengeluarkan state
class AuthCubit extends Cubit<AuthState> {
  AuthCubit(this._repository) : super(const AuthInitial());

  final AuthRepository _repository;

  Future<void> masuk(String email, String sandi) async {
    emit(const AuthLoading());
    try {
      final pengguna = await _repository.masuk(email, sandi);
      emit(AuthSuccess(pengguna));
    } on KesalahanAuth catch (e) {
      emit(AuthFailure(e.pesan));
    }
  }
}
dart
// ✅ UI yang memutuskan apa artinya state itu
BlocListener<AuthCubit, AuthState>(
  listener: (context, state) {
    if (state is AuthSuccess) {
      Navigator.of(context).pushNamedAndRemoveUntil('/home', (_) => false);
    } else if (state is AuthFailure) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.pesan)),
      );
    }
  },
  child: const FormMasuk(),
)

Keuntungannya nyata: AuthCubit bisa diuji tanpa membangun satu widget pun, dan bisa dipakai ulang di aplikasi lain dengan alur navigasi yang berbeda.

Untuk alur autentikasi, ada pola yang lebih bersih daripada BlocListener: biarkan struktur widget itu sendiri mencerminkan state.

dart
// lib/presentation/screens/auth_gate.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<AuthCubit, AuthState>(
      builder: (context, state) {
        return switch (state) {
          AuthInitial() || AuthLoading() => const SplashScreen(),
          AuthSuccess() => const HomeScreen(),
          AuthFailure() || AuthLoggedOut() => const LoginScreen(),
        };
      },
    );
  }
}

Tidak ada Navigator.push sama sekali. Ketika pengguna keluar akun, AuthCubit mengeluarkan AuthLoggedOut, dan LoginScreen otomatis muncul — seluruh tumpukan navigasi ikut hilang karena widget-nya diganti.

Pola ini menghilangkan seluruh kelas bug seperti "pengguna sudah keluar tetapi masih bisa menekan tombol kembali ke beranda".

Mengirim data lewat rute

Ketika layar butuh argumen:

dart
// Mengirim
Navigator.of(context).pushNamed('/detail', arguments: idProduk);
dart
// Di router
Route? bangunRute(RouteSettings settings) {
  return switch (settings.name) {
    '/detail' => MaterialPageRoute(
        builder: (_) {
          final id = settings.arguments as String;
          return BlocProvider<DetailCubit>(
            create: (ctx) => DetailCubit(
              ctx.read<ProdukRepository>(),
            )..muat(id),
            child: const DetailScreen(),
          );
        },
      ),
    _ => null,
  };
}

Perhatikan ..muat(id) — operator cascade dari Bab 2 yang membuat cubit langsung memuat datanya begitu dibuat.

Untuk keamanan tipe yang lebih baik, lebih disukai mengoper lewat konstruktor:

dart
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (ctx) => BlocProvider<DetailCubit>(
      create: (c) => DetailCubit(c.read<ProdukRepository>())..muat(idProduk),
      child: DetailScreen(idProduk: idProduk),
    ),
  ),
);

Membersihkan state saat keluar akun

Masalah nyata yang sering terlewat: pengguna A keluar, pengguna B masuk, dan data pengguna A masih terlihat karena BLoC global tidak pernah direset.

dart
class AuthCubit extends Cubit<AuthState> {
  AuthCubit(this._repository) : super(const AuthInitial());

  final AuthRepository _repository;

  Future<void> keluar() async {
    await _repository.keluar();
    emit(const AuthLoggedOut());
  }
}
dart
// Di level aplikasi, dengarkan dan bersihkan BLoC lain
BlocListener<AuthCubit, AuthState>(
  listenWhen: (sebelum, sesudah) => sesudah is AuthLoggedOut,
  listener: (context, state) {
    context.read<KeranjangCubit>().kosongkan();
    context.read<NotifikasiCubit>().reset();
    context.read<ProfilCubit>().reset();
  },
  child: const AuthGate(),
)

Alternatif yang lebih bersih untuk aplikasi besar: jangan sediakan BLoC berbasis pengguna secara global. Sediakan di bawah AuthGate, sehingga ketika pengguna keluar dan widget-nya diganti, BLoC-nya ikut dibuang otomatis.

dart
AuthSuccess() => MultiBlocProvider(
    providers: [
      // BLoC ini lahir saat login, mati saat logout
      BlocProvider(create: (c) => KeranjangCubit(c.read<KeranjangRepo>())),
      BlocProvider(create: (c) => ProfilCubit(c.read<ProfilRepo>())),
    ],
    child: const HomeScreen(),
  ),

Ini pemakaian arsitektur untuk menyelesaikan masalah yang biasanya diselesaikan dengan kode pembersihan manual.

Ringkasan tiga cakupan

┌─────────────────────────────────────────────────────────────────┐
│  LOKAL — BlocProvider(create:) di dalam satu layar              │
│  Hidup: selama layar terbuka                                    │
│  Untuk: formulir, pencarian, state sementara                    │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│  RUTE — BlocProvider.value(value:) saat push                    │
│  Hidup: dimiliki layar asal                                     │
│  Untuk: berbagi instance antara dua layar tertentu              │
│  ⚠️ Tidak memanggil close()                                      │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│  GLOBAL — MultiBlocProvider di atas MaterialApp                 │
│  Hidup: selama aplikasi berjalan                                │
│  Untuk: auth, tema, koneksi, keranjang                          │
└─────────────────────────────────────────────────────────────────┘

Diagram keputusannya:

Latihan Mandiri

Kerjakan salah satu, beberapa, atau semuanya secara berurutan untuk melatih pemahamanmu sampai benar-benar lekat.

Variasi 1: Keranjang ke Checkout — ⭐⭐ · 30–45 menit

Tantangan: Bangun alur dua layar: daftar keranjang → layar checkout. Layar checkout harus melihat instance keranjang yang sama dengan layar daftar, sehingga mengubah jumlah item di checkout langsung terlihat saat kembali. Lalu tambahkan tombol keluar akun yang membersihkan seluruh state dan membuat tombol kembali tidak bisa membawa pengguna ke halaman mana pun sebelumnya.

Bayangan alur layar:

text
[Daftar Keranjang: 2 item] ──push(.value)──▶ [Checkout: 2 item]
         ▲                    (INSTANCE SAMA)         │
         └──── ubah jumlah di checkout ────────────────┘
   kembali ke daftar → angka baru sudah terlihat di sini

...versi salah (create alih-alih .value) yang sengaja kamu coba lebih dulu:

text
[Daftar Keranjang: 2 item] ──push(create)──▶ [Checkout: 0 item]
                                               ← keranjang KOSONG,
                                                 tanpa error apa pun

Kriteria selesai:

  • Layar checkout menerima BLoC lewat BlocProvider.value, dengan context.read yang dipanggil memakai context layar asal.
  • Kamu sudah mencoba versi salahnya — memakai create alih-alih .value — dan mengamati bahwa layar kedua muncul dengan keranjang kosong tanpa satu pun pesan error.
  • BLoC autentikasi berada di atas MaterialApp, sementara BLoC keranjang tidak.
  • Keluar akun ditangani lewat navigasi reaktif — sebuah gerbang yang mengawasi state autentikasi — bukan dengan pushAndRemoveUntil manual di dalam callback tombol.

Petunjuk: Kunci untuk memahami bab ini adalah menyadari bahwa Navigator.push tidak menempatkan layar baru sebagai anak dari layar sekarang. Ia menempatkannya sebagai cabang baru dari Navigator, yang letaknya jauh lebih tinggi di pohon — karena itu ia tidak bisa "melihat ke atas" dan menemukan BlocProvider yang kamu pasang di layar asal. BlocProvider.value menyelesaikannya dengan memasang ulang instance yang sama di cabang baru itu. Perhatikan bahwa ia sengaja tidak memanggil close(), karena instance-nya milik orang lain — memanggil close() di sana akan mematikan keranjangmu saat checkout ditutup.

Variasi 2: Tiga Cakupan di Satu Aplikasi — ⭐⭐⭐ · 45–60 menit

Tantangan: Bangun kerangka aplikasi bengkel yang memakai ketiga cakupan sekaligus dan bisa kamu benarkan satu per satu: sesi pengguna sebagai BLoC global, daftar antrean servis sebagai BLoC per rute, dan formulir estimasi biaya sebagai BLoC lokal satu layar.

Bayangan cakupan ketiga BLoC:

text
Global (di atas MaterialApp)         → Sesi Pengguna
  hidup selama aplikasi berjalan

Per rute (BlocProvider.value)        → Antrean Servis
  dibagi ke layar-layar terkait, dibuang saat rute ditutup

Lokal (BlocProvider create)          → Estimasi Biaya
  hidup untuk satu layar formulir saja, dibuang saat layar ditutup

Kriteria selesai:

  • Ketiganya ada, dan untuk masing-masing kamu bisa menjelaskan dalam satu kalimat kenapa cakupannya bukan yang lain.
  • BLoC lokal benar-benar dibuang saat layarnya ditutup — dibuktikan dengan BlocObserver.
  • BLoC global tetap hidup saat berpindah-pindah layar.
  • Tidak ada satu pun BLoC yang dijadikan global hanya karena itu lebih mudah.

Petunjuk: Kriteria terakhir adalah godaan terbesar bab ini: menaruh semuanya di atas MaterialApp membuat semua masalah akses hilang seketika. Harganya baru terasa nanti — state yang tidak pernah dibersihkan, kebocoran antar pengguna setelah keluar akun, dan BLoC yang tetap bekerja padahal layarnya sudah lama ditutup. Aturan praktisnya: global hanya untuk yang benar-benar lintas aplikasi, seperti sesi dan tema.

Variasi 3: Gerbang Reaktif dan Pembersihan — ⭐⭐⭐ · 45–60 menit

Tantangan: Bangun aplikasi dengan tiga keadaan sesi — belum diperiksa, masuk, keluar — dan sebuah gerbang yang memilih layar berdasarkan itu. Lalu pastikan keluar akun benar-benar membersihkan seluruh state berbasis pengguna, dan masuk dengan akun lain tidak memperlihatkan satu pun data pengguna sebelumnya.

Bayangan alur layar:

text
Sesi: belum diperiksa  ──▶ [Splash]
Sesi: masuk            ──▶ [Beranda Pengguna A]
Sesi: keluar           ──▶ [Layar Masuk]

masuk sebagai A → keluar → masuk sebagai B:

text
[Beranda Pengguna A] ──keluar──▶ [Masuk] ──masuk sbg B──▶ [Beranda Pengguna B]
                                                             (TIDAK ada sisa
                                                              data milik A)

Kriteria selesai:

  • Gerbang memakai switch pada state sesi tanpa cabang default.
  • Keadaan "belum diperiksa" menampilkan splash, sehingga layar masuk tidak berkedip saat sesi sedang dipulihkan.
  • Masuk sebagai pengguna A, keluar, lalu masuk sebagai pengguna B tidak menampilkan satu pun data milik A.
  • Tidak ada pushAndRemoveUntil di seluruh alur autentikasi.

Petunjuk: Kriteria ketiga adalah bug yang sangat sering lolos ke produksi, karena hanya muncul kalau kamu benar-benar mengujinya dengan dua akun. Ada dua pendekatan: membersihkan tiap BLoC berbasis pengguna saat keluar, atau — biasanya lebih andal — membuat ulang seluruh subpohon yang berisi BLoC itu, sehingga tidak ada yang perlu diingat untuk dibersihkan. Yang kedua bekerja dengan sendirinya kalau gerbangmu memang menukar subpohon berdasarkan state.

Variasi 4: Rute Bernama dengan BLoC — ⭐⭐⭐⭐ · 60–90 menit

Tantangan: Bangun aplikasi lima layar dengan rute bernama, di mana dua di antaranya menerima argumen dan tiga di antaranya butuh BLoC dari layar sebelumnya. Tangani juga kasus rute yang dibuka langsung dari deep link tanpa melewati layar induknya.

Bayangan alur layar:

text
Alur normal:    [Daftar] ──push('/detail')──▶ [Detail]
                 (BLoC ada)                    (pakai BLoC yang sama)

Dari deep link (aplikasi baru dibuka langsung ke /detail):
                          [Detail]
                             │  BLoC tidak ditemukan
                             ▼  → buat BARU sendiri
                        (tetap berfungsi normal)

Argumen salah tipe atau hilang → [Layar Kesalahan], bukan crash merah.

Kriteria selesai:

  • Argumen rute divalidasi, dan argumen yang salah tipe atau hilang menghasilkan layar kesalahan, bukan crash.
  • Layar yang dibuka dari deep link tanpa induknya tetap berfungsi — ia membuat BLoC-nya sendiri kalau tidak menemukan yang sudah ada.
  • Tabel rute dibuat sekali sebagai properti final, bukan di dalam build().
  • Kamu bisa menyebut satu hal yang hilang dibandingkan MaterialPageRoute.

Petunjuk: Kriteria kedua adalah persoalan nyata yang muncul begitu aplikasimu punya deep link atau notifikasi yang membuka layar tertentu: layar detail yang biasanya menerima BLoC dari daftar tiba-tiba dibuka sendirian. Pola yang bekerja adalah membuat layar itu mampu berdiri sendiri — coba ambil BLoC yang ada, dan buat baru kalau tidak ketemu. Ini juga membuat layar itu jauh lebih mudah diuji.

Ikhtisar

  • Navigator.push menempatkan layar baru sebagai rute yang bercabang dari Navigator, bukan sebagai anak layar sebelumnya — karena itu ia kehilangan akses ke BlocProvider di layar asal.
  • Akses lokal (BlocProvider(create:)) — BLoC hidup selama satu layar, dibuang otomatis saat ditutup.
  • Akses rute (BlocProvider.value(value:)) — berbagi instance yang sama ke layar baru. Panggil context.read memakai context layar asal.
  • BlocProvider.value tidak memanggil close() karena instance-nya dimiliki orang lain.
  • Memakai create alih-alih .value untuk layar kedua menghasilkan instance baru dengan state kosong — bug yang tidak menimbulkan error apa pun.
  • Akses global — taruh MultiBlocProvider di atas MaterialApp agar Navigator dan seluruh rutenya tercakup.
  • Global hanya untuk yang benar-benar lintas aplikasi. Sisanya lokal atau per rute.
  • Buat objek router sekali sebagai properti final, bukan di dalam build().
  • BLoC tidak boleh melakukan navigasi. Ia mengeluarkan state; BlocListener atau BlocBuilder yang memutuskan artinya.
  • Navigasi reaktif (AuthGate dengan switch pada state) menghilangkan seluruh kelas bug seperti bisa kembali ke halaman setelah keluar akun.
  • Bersihkan BLoC berbasis pengguna saat keluar akun — atau lebih baik, sediakan di bawah gerbang autentikasi agar terbuang otomatis.

Berikutnya: Bab 24 — Komunikasi Antar-BLoC.

Transkrip asli

Disintesis dari 2_flutter_bloc-zero-to-hero/6_bloc-access-and-navigation.md (local, route, dan global access dengan BlocProvider serta BlocProvider.value) dan 8_build-context-in-depth.md. Lihat PDF BLoC Zero to Hero.

Rangkuman pembelajaran pribadi, disusun ulang dari beberapa kursus Flutter.