Skip to content

Bab 24 — Komunikasi Antar-BLoC

Cepat atau lambat, aplikasimu akan punya dua BLoC yang perlu saling tahu. Status koneksi internet berubah, dan penghitung harus bereaksi. Pengguna keluar akun, dan keranjang belanja harus dikosongkan. Filter berubah, dan daftar harus dimuat ulang.

Bab ini membahas dua cara menghubungkan mereka — beserta jebakan yang membuat salah satu cara menjadi pilihan buruk di sebagian besar kasus.

Kasus yang akan kita bangun

Kita punya dua Cubit:

  • InternetCubit — memantau koneksi, mengeluarkan InternetConnected(wifi), InternetConnected(seluler), atau InternetDisconnected.
  • CounterCubit — penghitung sederhana.

Aturannya: ketika ponsel tersambung ke Wi-Fi, penghitung bertambah satu. Ketika tersambung ke data seluler, penghitung berkurang satu.

   InternetCubit                      CounterCubit
   ─────────────                      ────────────
   Wi-Fi tersambung  ──────────────►  increment()
   Data seluler      ──────────────►  decrement()

Pertanyaannya: bagaimana satu Cubit mendengarkan Cubit lain?

Ingat dari Bab 20 bahwa setiap Cubit dan Bloc mengeluarkan stream of states. Karena itu, Cubit lain bisa berlangganan stream itu dan bereaksi terhadapnya.

Ada dua cara melakukannya.

Cara 1: berlangganan dari dalam Cubit

Cubit tujuan menerima Cubit sumber lewat konstruktor, lalu berlangganan stream-nya.

dart
// lib/logic/cubit/counter_cubit.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'internet_cubit.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit({required this.internetCubit}) : super(0) {
    pantauInternetCubit();
  }

  final InternetCubit internetCubit;
  StreamSubscription<InternetState>? _langganan;

  void pantauInternetCubit() {
    _langganan = internetCubit.stream.listen((internetState) {
      if (internetState is InternetConnected &&
          internetState.jenis == JenisKoneksi.wifi) {
        increment();
      } else if (internetState is InternetConnected &&
          internetState.jenis == JenisKoneksi.seluler) {
        decrement();
      }
    });
  }

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);

  @override
  Future<void> close() {
    _langganan?.cancel();      // ⚠️ wajib — kalau tidak, memori bocor
    return super.close();
  }
}

Merangkainya di main.dart butuh perhatian pada urutan:

dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';

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

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        // 1. InternetCubit harus lebih dulu
        BlocProvider<InternetCubit>(
          create: (_) => InternetCubit(connectivity: Connectivity()),
        ),

        // 2. CounterCubit mengambilnya dari context
        BlocProvider<CounterCubit>(
          create: (context) => CounterCubit(
            internetCubit: context.read<InternetCubit>(),
          ),
        ),
      ],
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: const HomeScreen(),
      ),
    );
  }
}

Kalau CounterCubit dideklarasikan lebih dulu, context.read<InternetCubit>() akan gagal karena InternetCubit belum tersedia.

Kelebihan dan kekurangan

Kelebihan: logikanya terpusat di satu tempat. Siapa pun yang membaca CounterCubit langsung tahu bahwa ia bereaksi terhadap koneksi.

Kekurangan yang serius: CounterCubit sekarang bergantung pada InternetCubit. Ia tidak bisa dipakai tanpanya, dan untuk mengujinya kamu harus menyediakan InternetCubit palsu.

Kekurangan itu makin terasa ketika rantainya bertambah:

   ❌ Ketergantungan yang saling terkait
   InternetCubit ──► CounterCubit ──► SettingsCubit ──► ProfileCubit

Ketika kamu ingin menguji ProfileCubit, kamu harus membangun keempatnya.

Waspadai ketergantungan melingkar

Kalau A berlangganan B dan B berlangganan A, kamu menciptakan lingkaran tak berujung: A memicu B, B memicu A, dan seterusnya sampai aplikasi membeku.

Ini tidak menghasilkan error yang jelas — aplikasi hanya berhenti merespons. Kalau kamu merasa butuh komunikasi dua arah, itu pertanda kuat bahwa keduanya seharusnya menjadi satu BLoC.

Cara 2: menghubungkan lewat lapisan presentation

Cara kedua membalik tanggung jawabnya. Kedua Cubit tetap tidak saling mengenal; widget yang menjembatani mereka.

dart
// CounterCubit sekarang bersih — tidak tahu apa-apa tentang internet
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}
dart
// lib/presentation/screens/home_screen.dart
BlocListener<InternetCubit, InternetState>(
  listener: (context, state) {
    if (state is InternetConnected && state.jenis == JenisKoneksi.wifi) {
      context.read<CounterCubit>().increment();
    } else if (state is InternetConnected &&
        state.jenis == JenisKoneksi.seluler) {
      context.read<CounterCubit>().decrement();
    }
  },
  child: const IsiHomeScreen(),
)

Kelebihan dan kekurangan

Kelebihan: kedua Cubit sepenuhnya mandiri. Masing-masing bisa diuji sendiri tanpa mock. Keduanya bisa dipakai ulang di aplikasi lain.

Kekurangan: logika bisnis bocor ke lapisan presentation — sesuatu yang seharusnya dihindari menurut Bab 22. Dan kalau listener itu berada di sebuah layar, ia berhenti bekerja ketika layar itu ditutup.

Kekurangan terakhir itu sering luput. Kalau kamu memilih cara ini untuk aturan yang harus berlaku sepanjang aplikasi berjalan, taruh listener-nya di level aplikasi:

dart
MultiBlocProvider(
  providers: [ /* ... */ ],
  child: BlocListener<InternetCubit, InternetState>(
    listener: (context, state) { /* ... */ },
    child: MaterialApp(
      home: const HomeScreen(),
    ),
  ),
)

Membandingkan keduanya

   CARA 1: Langganan langsung          CARA 2: Lewat presentation
   ─────────────────────────           ──────────────────────────

   ┌─────────────────┐                 ┌─────────────────┐
   │  InternetCubit  │                 │  InternetCubit  │
   └────────┬────────┘                 └────────┬────────┘
            │ stream                            │ state
            ▼                                   ▼
   ┌─────────────────┐                 ┌─────────────────┐
   │  CounterCubit   │                 │  BlocListener   │
   │  (bergantung)   │                 │   (widget)      │
   └─────────────────┘                 └────────┬────────┘
                                                │ increment()

                                       ┌─────────────────┐
                                       │  CounterCubit   │
                                       │   (mandiri)     │
                                       └─────────────────┘

   ✅ Logika terpusat                   ✅ Cubit saling mandiri
   ✅ Berlaku sepanjang app hidup       ✅ Mudah diuji terpisah
   ❌ Ketergantungan antar Cubit        ❌ Logika bisnis di UI
   ❌ Lebih sulit diuji                 ❌ Mati bila layar ditutup

Rekomendasi praktis

Untuk sebagian besar kasus, cara 1 lebih disukai — karena aturan bisnis seharusnya berada di lapisan business logic, bukan di widget.

Pakai cara 2 hanya ketika reaksinya memang bersifat presentasi: menampilkan snackbar, membuka dialog, atau berpindah halaman. Untuk hal-hal itu, Navigator dan ScaffoldMessenger memang milik lapisan UI.

Pola yang lebih baik: repository bersama

Ada cara ketiga yang sering lebih bersih daripada keduanya, terutama ketika dua BLoC butuh data yang sama, bukan sekadar bereaksi terhadap event.

Alih-alih menghubungkan BLoC ke BLoC, hubungkan keduanya ke repository yang sama.

dart
// lib/data/repositories/auth_repository.dart
import 'dart:async';
import '../models/pengguna.dart';

class AuthRepository {
  final _pengendali = StreamController<Pengguna?>.broadcast();

  Pengguna? _penggunaSaatIni;

  /// Stream yang bisa didengarkan banyak BLoC sekaligus.
  Stream<Pengguna?> get aliranPengguna => _pengendali.stream;

  Pengguna? get penggunaSaatIni => _penggunaSaatIni;

  Future<void> masuk(String email, String sandi) async {
    final pengguna = await _api.masuk(email, sandi);
    _penggunaSaatIni = pengguna;
    _pengendali.add(pengguna);      // semua pendengar menerima ini
  }

  Future<void> keluar() async {
    await _api.keluar();
    _penggunaSaatIni = null;
    _pengendali.add(null);
  }

  void tutup() => _pengendali.close();
}

Perhatikan .broadcast(). Tanpa itu, StreamController hanya mengizinkan satu pendengar — dan BLoC kedua yang mencoba mendengarkan akan mendapat error.

Sekarang beberapa BLoC bisa mendengarkan sumber yang sama, tanpa saling mengenal:

dart
class ProfilCubit extends Cubit<ProfilState> {
  ProfilCubit(this._authRepo) : super(const ProfilKosong()) {
    _langganan = _authRepo.aliranPengguna.listen((pengguna) {
      if (pengguna == null) {
        emit(const ProfilKosong());
      } else {
        emit(ProfilTerisi(pengguna));
      }
    });
  }

  final AuthRepository _authRepo;
  StreamSubscription<Pengguna?>? _langganan;

  @override
  Future<void> close() {
    _langganan?.cancel();
    return super.close();
  }
}

class KeranjangCubit extends Cubit<KeranjangState> {
  KeranjangCubit(this._authRepo) : super(const KeranjangKosong()) {
    _langganan = _authRepo.aliranPengguna.listen((pengguna) {
      // Kosongkan keranjang ketika pengguna keluar
      if (pengguna == null) emit(const KeranjangKosong());
    });
  }

  final AuthRepository _authRepo;
  StreamSubscription<Pengguna?>? _langganan;

  @override
  Future<void> close() {
    _langganan?.cancel();
    return super.close();
  }
}

Sekarang strukturnya:

Tidak ada satu pun BLoC yang mengenal BLoC lain. Semuanya hanya mengenal repository. Ini yang disebut komunikasi lewat lapisan data, dan inilah pola yang paling banyak dipakai di aplikasi produksi berskala besar.

Kenapa ini lebih baik

  • Menambah BLoC keempat yang butuh data pengguna tidak mengubah apa pun.
  • Setiap BLoC bisa diuji dengan repository palsu — jauh lebih sederhana daripada memalsukan BLoC lain beserta seluruh state-nya.
  • Tidak mungkin terjadi ketergantungan melingkar, karena arahnya selalu satu: BLoC → repository.

Contoh lengkap: pengaturan yang bergantung pada koneksi

Mari bangun kasus yang lebih realistis. SettingsCubit mengelola sakelar pengaturan, tetapi opsi "unduh otomatis" harus dinonaktifkan ketika pengguna memakai data seluler.

dart
// lib/logic/cubit/settings_state.dart
import 'package:equatable/equatable.dart';

class SettingsState extends Equatable {
  const SettingsState({
    this.notifikasiAplikasi = false,
    this.notifikasiEmail = false,
    this.unduhOtomatis = false,
    this.unduhOtomatisDiizinkan = true,
  });

  final bool notifikasiAplikasi;
  final bool notifikasiEmail;
  final bool unduhOtomatis;

  /// Salah ketika koneksi memakai data seluler.
  final bool unduhOtomatisDiizinkan;

  SettingsState copyWith({
    bool? notifikasiAplikasi,
    bool? notifikasiEmail,
    bool? unduhOtomatis,
    bool? unduhOtomatisDiizinkan,
  }) {
    return SettingsState(
      notifikasiAplikasi: notifikasiAplikasi ?? this.notifikasiAplikasi,
      notifikasiEmail: notifikasiEmail ?? this.notifikasiEmail,
      unduhOtomatis: unduhOtomatis ?? this.unduhOtomatis,
      unduhOtomatisDiizinkan:
          unduhOtomatisDiizinkan ?? this.unduhOtomatisDiizinkan,
    );
  }

  @override
  List<Object?> get props => [
        notifikasiAplikasi,
        notifikasiEmail,
        unduhOtomatis,
        unduhOtomatisDiizinkan,
      ];
}
dart
// lib/logic/cubit/settings_cubit.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'internet_cubit.dart';
import 'settings_state.dart';

class SettingsCubit extends Cubit<SettingsState> {
  SettingsCubit({required this.internetCubit})
      : super(const SettingsState()) {
    _pantauKoneksi();
  }

  final InternetCubit internetCubit;
  StreamSubscription<InternetState>? _langganan;

  void _pantauKoneksi() {
    _langganan = internetCubit.stream.listen((internetState) {
      final diizinkan = internetState is InternetConnected &&
          internetState.jenis == JenisKoneksi.wifi;

      emit(
        state.copyWith(
          unduhOtomatisDiizinkan: diizinkan,
          // Matikan otomatis kalau tidak lagi diizinkan
          unduhOtomatis: diizinkan ? state.unduhOtomatis : false,
        ),
      );
    });
  }

  void setNotifikasiAplikasi(bool aktif) {
    emit(state.copyWith(notifikasiAplikasi: aktif));
  }

  void setNotifikasiEmail(bool aktif) {
    emit(state.copyWith(notifikasiEmail: aktif));
  }

  void setUnduhOtomatis(bool aktif) {
    // Aturan bisnis ditegakkan di sini, bukan di UI
    if (!state.unduhOtomatisDiizinkan) return;
    emit(state.copyWith(unduhOtomatis: aktif));
  }

  @override
  Future<void> close() {
    _langganan?.cancel();
    return super.close();
  }
}

Perhatikan setUnduhOtomatis yang menolak permintaan ketika belum diizinkan. Aturan itu berada di business logic, sehingga tetap ditegakkan meskipun ada bug di UI yang membiarkan tombolnya bisa ditekan.

Layarnya:

dart
// lib/presentation/screens/settings_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../logic/cubit/settings_cubit.dart';
import '../../logic/cubit/settings_state.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Pengaturan')),
      body: BlocConsumer<SettingsCubit, SettingsState>(
        listenWhen: (sebelum, sesudah) =>
            sebelum.unduhOtomatisDiizinkan &&
            !sesudah.unduhOtomatisDiizinkan,
        listener: (context, state) {
          ScaffoldMessenger.of(context)
            ..clearSnackBars()
            ..showSnackBar(
              const SnackBar(
                content: Text(
                  'Unduh otomatis dimatikan karena kamu memakai data seluler.',
                ),
              ),
            );
        },
        builder: (context, state) {
          final cubit = context.read<SettingsCubit>();

          return ListView(
            children: [
              SwitchListTile(
                value: state.notifikasiAplikasi,
                onChanged: cubit.setNotifikasiAplikasi,
                title: const Text('Notifikasi Aplikasi'),
                secondary: const Icon(Icons.notifications),
              ),
              SwitchListTile(
                value: state.notifikasiEmail,
                onChanged: cubit.setNotifikasiEmail,
                title: const Text('Notifikasi Email'),
                secondary: const Icon(Icons.email),
              ),
              const Divider(),
              SwitchListTile(
                value: state.unduhOtomatis,
                // null menonaktifkan sakelar — pola dari Bab 6
                onChanged: state.unduhOtomatisDiizinkan
                    ? cubit.setUnduhOtomatis
                    : null,
                title: const Text('Unduh Otomatis'),
                subtitle: Text(
                  state.unduhOtomatisDiizinkan
                      ? 'Unduh konten baru secara otomatis.'
                      : 'Hanya tersedia saat tersambung Wi-Fi.',
                ),
                secondary: const Icon(Icons.download),
              ),
            ],
          );
        },
      ),
    );
  }
}

Perhatikan onChanged: ... : null — memberi null menonaktifkan sakelar dan membuatnya abu-abu secara otomatis. Pola yang sudah kita bahas di Bab 6, muncul lagi di sini.

Bloc yang mendengarkan Bloc

Kalau kamu memakai Bloc alih-alih Cubit, polanya sedikit berbeda: alih-alih memanggil metode, kamu menambahkan event.

dart
class TodoBloc extends Bloc<TodoEvent, TodoState> {
  TodoBloc({required this.authBloc}) : super(const TodoInitial()) {
    on<MuatTodo>(_onMuatTodo);
    on<TodoDireset>(_onReset);

    // Terjemahkan state AuthBloc menjadi event TodoBloc
    _langganan = authBloc.stream.listen((authState) {
      if (authState is AuthLoggedOut) {
        add(TodoDireset());
      } else if (authState is AuthSuccess) {
        add(MuatTodo(authState.pengguna.id));
      }
    });
  }

  final AuthBloc authBloc;
  StreamSubscription<AuthState>? _langganan;

  Future<void> _onMuatTodo(MuatTodo event, Emitter<TodoState> emit) async {
    emit(const TodoLoading());
    try {
      final daftar = await _repo.ambilUntuk(event.idPengguna);
      emit(TodoLoaded(daftar));
    } catch (_) {
      emit(const TodoError('Gagal memuat tugas.'));
    }
  }

  void _onReset(TodoDireset event, Emitter<TodoState> emit) {
    emit(const TodoInitial());
  }

  @override
  Future<void> close() {
    _langganan?.cancel();
    return super.close();
  }
}

Keunggulan pendekatan berbasis event terlihat di sini: setiap perubahan tetap melewati handler yang sama, sehingga logikanya tidak terduplikasi antara "dipicu pengguna" dan "dipicu BLoC lain". Dan riwayat event-nya tetap bisa dilacak — lihat Bab 26.

Kesalahan yang sering terjadi

Lupa membatalkan langganan.

dart
// ❌ Stream tetap hidup setelah Cubit ditutup
class CounterCubit extends Cubit<int> {
  CounterCubit(this.internetCubit) : super(0) {
    internetCubit.stream.listen((state) { /* ... */ });
  }
  // tidak ada close() yang di-override
}

Setiap listen wajib disimpan sebagai StreamSubscription dan dibatalkan di close().

Memakai StreamController tanpa broadcast.

dart
// ❌ Pendengar kedua akan melempar error
final _pengendali = StreamController<Pengguna?>();

// ✅
final _pengendali = StreamController<Pengguna?>.broadcast();

Salah urutan di MultiBlocProvider. BLoC yang menjadi ketergantungan harus dideklarasikan lebih dulu.

Ketergantungan melingkar. A mendengarkan B, B mendengarkan A. Kalau ini terjadi, gabungkan keduanya.

Terlalu banyak BLoC saling terhubung. Kalau lebih dari dua BLoC saling mendengarkan, itu pertanda kuat bahwa kamu butuh repository bersama.

Latihan Mandiri

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

Variasi 1: Repository Koneksi Bersama — ⭐⭐ · 45–60 menit

Tantangan: Buat dua BLoC yang harus bereaksi terhadap hal yang sama: sebuah PengaturanCubit dan sebuah UnduhanCubit, keduanya perlu tahu status koneksi jaringan. Implementasikan dengan repository bersama — satu sumber stream yang didengarkan keduanya, tanpa kedua Cubit itu saling mengenal.

Kriteria selesai:

  • KoneksiRepository mengekspos satu Stream status, dan StreamController di dalamnya adalah .broadcast().
  • Kedua Cubit berlangganan di konstruktornya, dan membatalkan langganan di close() yang di-override.
  • Tidak ada satu pun impor silang antara kedua Cubit itu.
  • Aturan "unduhan tidak boleh dimulai saat offline" ditegakkan di dalam Cubit dengan penjagaan awal, bukan hanya dengan menonaktifkan tombol di UI.

Petunjuk: Kalau kamu mendapat error bahwa stream sudah punya pendengar, itu tanda StreamController-nya belum .broadcast() — controller biasa hanya mengizinkan satu langganan. Untuk kriteria terakhir, alasannya praktis: tombol yang dinonaktifkan hanya menutup satu pintu masuk. Aksi yang sama bisa dipicu dari notifikasi, dari deep link, atau dari percobaan ulang otomatis, dan tak satu pun dari itu melewati tombolmu. Menaruh if (!daring) return; di awal metode Cubit menutup semuanya sekaligus. Terakhir: kalau kamu merasa kedua Cubit ini sebenarnya perlu saling mengetahui state satu sama lain secara detail, berhenti sejenak dan pertimbangkan apakah mereka memang seharusnya satu BLoC.

Variasi 2: Kasir dan Stok — ⭐⭐⭐ · 45–60 menit

Tantangan: Bangun KasirCubit dan StokCubit di mana setiap transaksi kasir harus mengurangi stok. Kali ini implementasikan dengan langganan langsung: StokCubit menerima KasirCubit lewat konstruktor dan mendengarkan stream-nya.

Kriteria selesai:

  • StokCubit menerima KasirCubit lewat konstruktor, bukan mencarinya lewat context.
  • Langganan dibatalkan di close() yang di-override, dan kamu sudah membuktikan close() benar-benar dipanggil.
  • Urutan deklarasi di MultiBlocProvider benar — dan kamu sudah mencoba membaliknya untuk melihat apa yang terjadi.
  • Transaksi yang melebihi stok tersedia ditolak sebelum sempat mengurangi apa pun.

Petunjuk: Membalik urutan di MultiBlocProvider menghasilkan error yang cukup jelas, dan mengalaminya sekali membuat aturannya melekat: yang bergantung harus dideklarasikan setelah yang dibergantungi. Bandingkan hasil latihan ini dengan Variasi 1 — pendekatan langganan langsung menaruh logika di satu tempat dan tetap bekerja meski layarnya ditutup, tetapi menciptakan ketergantungan antar-BLoC yang membuat keduanya lebih sulit diuji sendiri-sendiri.

Variasi 3: Jembatan Lewat Presentation — ⭐⭐⭐ · 45–60 menit

Tantangan: Ambil kasus kasir dan stok dari Variasi 2, lalu implementasikan ulang dengan cara kedua: BlocListener di widget yang menjembatani keduanya. Setelah itu, tulis perbandingan jujur kapan masing-masing pantas dipakai.

Kriteria selesai:

  • Kedua Cubit tidak saling mengenal sama sekali.
  • Kamu sudah menguji apa yang terjadi ketika layar penjembatannya ditutup di tengah transaksi, dan mencatat hasilnya.
  • Perbandinganmu menyebut minimal satu kasus di mana cara ini lebih baik dan satu kasus di mana ia lebih buruk.
  • Efek yang murni presentasi — snackbar dan dialog — tetap memakai cara ini, bukan dipindah ke dalam BLoC.

Petunjuk: Kriteria kedua adalah kelemahan terbesar pendekatan ini dan sekaligus pelajarannya: BlocListener hanya hidup selama widgetnya ada di pohon, sehingga aturan bisnis yang dititipkan padanya akan berhenti bekerja ketika pengguna berpindah layar. Itulah kenapa bab ini menyimpulkan aturan bisnis sebaiknya di dalam BLoC, dan menyisakan cara ini untuk reaksi yang memang bersifat tampilan.

Variasi 4: Memutus Lingkaran — ⭐⭐⭐⭐ · 60–90 menit

Tantangan: Bangun dengan sengaja dua Cubit yang saling bergantung — satu memegang keranjang, satu memegang promo, dan masing-masing perlu tahu keadaan yang lain untuk menghitung hasilnya. Amati apa yang terjadi, lalu perbaiki dengan tiga pendekatan berbeda dan pilih satu.

Kriteria selesai:

  • Versi melingkar dibuat lebih dulu, dan kamu mencatat gejalanya — apakah ia gagal saat kompilasi, saat berjalan, atau justru "bekerja" tapi salah.
  • Ketiga perbaikan dicoba: menggabungkan menjadi satu BLoC, memindahkan logika bersama ke repository, dan memutus satu arah ketergantungan.
  • Kamu memilih satu dan menjelaskan alasannya.
  • Versi akhir tidak punya satu pun impor silang antar-Cubit.

Petunjuk: Ketergantungan melingkar antar-BLoC hampir selalu adalah gejala, bukan penyakit — ia berarti kamu memecah satu konsep menjadi dua padahal keduanya sebenarnya satu keputusan. "Berapa total yang harus dibayar setelah promo" adalah satu pertanyaan, bukan dua. Kalau setelah dicoba ketiganya kamu tetap merasa keduanya perlu terpisah, repository bersama adalah jalan tengah yang paling sering benar.

Ikhtisar

  • Setiap Cubit dan Bloc mengeluarkan stream of states, sehingga BLoC lain bisa berlangganan dan bereaksi terhadapnya.
  • Cara 1 — langganan langsung: BLoC tujuan menerima BLoC sumber lewat konstruktor dan listen ke stream-nya. Logika terpusat, tetapi menciptakan ketergantungan antar BLoC.
  • Cara 2 — lewat presentation: BlocListener di widget yang menjembatani. BLoC tetap mandiri, tetapi logika bisnis bocor ke UI dan berhenti bekerja ketika layarnya ditutup.
  • Untuk aturan bisnis, cara 1 lebih disukai. Cara 2 untuk reaksi yang memang bersifat presentasi — snackbar, dialog, navigasi.
  • Di MultiBlocProvider, urutan penting: ketergantungan harus dideklarasikan lebih dulu.
  • Setiap listen wajib dibatalkan di close() yang di-override.
  • Pola terbaik untuk kasus kompleks: repository bersama. Beberapa BLoC mendengarkan satu Stream dari repository, tanpa saling mengenal.
  • StreamController harus .broadcast() kalau akan didengarkan lebih dari satu BLoC.
  • Hindari ketergantungan melingkar. Kalau A dan B perlu saling tahu, mereka seharusnya satu BLoC.
  • Tegakkan aturan bisnis di dalam BLoC (if (!diizinkan) return;), jangan hanya mengandalkan UI yang menonaktifkan tombol.

Berikutnya: Bab 25 — HydratedBloc, agar state bertahan setelah aplikasi ditutup.

Transkrip asli

Disintesis dari 2_flutter_bloc-zero-to-hero/7_bloc-to-bloc-communication.md (dua metode komunikasi antar cubit, contoh InternetCubit ↔ CounterCubit). Lihat PDF BLoC Zero to Hero.

Rangkuman pembelajaran pribadi, disusun ulang dari beberapa kursus Flutter.