Stream
Aliran data dari waktu ke waktu: membuat Stream dengan async*/yield, mendengarkan dengan listen dan await for, transformasi (map, where, take), StreamController, dan broadcast.
Future satu nilai, Stream banyak nilai
Future menghasilkan satu nilai di masa depan. Stream menghasilkan banyak nilai sepanjang waktu. Bayangkan pipa air: air (data) mengalir terus, dan kamu memasang keran untuk menampungnya. Contoh: detik jam yang berjalan, pesan chat yang masuk, lokasi GPS yang berubah, progres unduhan.
Membuat Stream dengan async* dan yield
Stream<int> hitungMundur(int dari) async* {
for (int i = dari; i > 0; i--) {
await Future.delayed(Duration(milliseconds: 500));
yield i; // kirim satu nilai ke aliran
}
}
Function bertanda async* mengembalikan Stream; setiap yield mengirim satu elemen. (yield* meneruskan seluruh Stream lain.)
Mendengarkan: await for
Future<void> main() async {
await for (final angka in hitungMundur(3)) {
print(angka); // 3, 2, 1
}
print('Selesai');
}
await for mengulang setiap kali ada data baru, dan selesai saat Stream berakhir. Ini cara paling mudah dibaca.
Mendengarkan: listen
var langganan = hitungMundur(3).listen(
(data) => print('Data: $data'),
onError: (e) => print('Error: $e'),
onDone: () => print('Aliran selesai'),
);
// langganan.pause(); langganan.resume(); langganan.cancel();
listen tidak memblokir; ia mendaftarkan callback. Objek StreamSubscription yang dikembalikan bisa dijeda atau dibatalkan. Selalu batalkan langganan bila sudah tidak dipakai agar tidak ada kebocoran memori.
Mengolah Stream
Mirip koleksi, Stream punya operator transformasi yang menghasilkan Stream baru:
var angka = Stream.fromIterable([1, 2, 3, 4, 5, 6]);
await for (var n in angka.where((n) => n.isEven).map((n) => n * 10)) {
print(n); // 20, 40, 60
}
await angka.take(3).toList(); // [1, 2, 3]
await angka.length; // 6
await angka.first; // 1
var jumlah = await angka.fold(0, (a, b) => a + b); // 21
| Operator | Fungsi |
|---|---|
map | Ubah tiap elemen |
where | Saring elemen |
take(n) / skip(n) | Ambil / lewati n elemen pertama |
distinct() | Buang elemen berurutan yang sama |
toList(), first, last, length | Mengumpulkan hasil (mengembalikan Future) |
StreamController: membuat sumber sendiri
import 'dart:async';
var pengendali = StreamController<String>();
pengendali.stream.listen((pesan) => print('Diterima: $pesan'));
pengendali.add('Halo');
pengendali.add('Dart');
pengendali.addError('Ada masalah'); // kirim error ke aliran
await pengendali.close(); // tandai aliran selesai
Controller adalah "keran + pipa" sekaligus: kamu add data dari satu sisi, dan pendengar menerima dari sisi lain. Ini dipakai untuk membangun fitur seperti chat, notifikasi, dan state management.
Single-subscription vs broadcast
| Biasa | Broadcast | |
|---|---|---|
| Jumlah pendengar | Satu saja | Banyak |
| Membuat | StreamController() | StreamController.broadcast() |
| Data sebelum ada pendengar | Ditahan | Hilang |
Memasang dua listen pada Stream biasa menghasilkan error Bad state: Stream has already been listened to. Ubah ke .asBroadcastStream() bila perlu banyak pendengar.
Periodik
var tiap = Stream.periodic(Duration(seconds: 1), (n) => n); // 0, 1, 2, ...
await for (var detik in tiap.take(3)) {
print('Detik ke-$detik');
}
Future atau Stream?
| Situasi | Pilih |
|---|---|
| Satu hasil (respons API, baca berkas) | Future |
| Rangkaian peristiwa / data berkelanjutan | Stream |
Error umum
- Program tidak berhenti →
StreamControllertidak di-close(), atau langganan tidak dibatalkan. Bad state: Cannot add event after closing→addsetelahclose.- Memakai
await fordi function yang tidakasync.