Pada chapter sebelumnya, kita memakai Channel untuk komunikasi satu-ke-satu antar coroutine. Bagaimana jika satu sumber event ingin memberi tahu banyak subscriber sekaligus? Pola ini umum di aplikasi nyata: event UI, notifikasi real-time, atau update harga saham. SharedFlow adalah jawaban Kotlin — hot multicast stream di mana semua collector aktif menerima nilai yang sama. Bab ini mengenalkan konsep SharedFlow, perbedaan dengan Channel, dan dua konfigurasi utama: replay dan buffer overflow.
Apa itu SharedFlow #
SharedFlow<T> adalah sebuah hot stream — ia aktif terlepas dari ada atau tidaknya collector. Saat sebuah nilai di-emit, semua collector yang sedang aktif menerima nilai tersebut. Karakteristik utama:
| Aspek | Detail |
|---|---|
| Tipe stream | Hot — hidup terlepas dari collector |
| Multicast | Satu emit diterima semua collector aktif |
| Replay buffer | Nilai terakhir (configurable) dapat di-replay ke collector baru |
| Backpressure | Configurable via onBufferOverflow (SUSPEND, DROP_OLDEST, DROP_LATEST) |
| Default | MutableSharedFlow<T>() → replay=0, extraBufferCapacity=0 |
Perbedaan dengan Channel:
| Aspek | Channel | SharedFlow |
|---|---|---|
| Jumlah consumer | Satu | Banyak (multicast) |
| Hidup tanpa consumer | Tidak (akan block) | Ya (nilai bisa hilang jika tidak ada collector) |
| Replay | Tidak ada | Configurable |
| Idiomatic use | Komunikasi 1-1 | Event broadcast |
Demonstrasi Multicast #
Kode berikut mendemonstrasikan dua fitur SharedFlow: multicast dan replay buffer:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
// 1. SharedFlow multicast: banyak collector menerima emit yang sama
println("=== 1. SharedFlow multicast ===")
val shared = MutableSharedFlow<String>(replay = 0)
// Collector A mulai
val jobA = launch {
shared.collect { println("[A] terima: $it") }
}
// Collector B mulai
val jobB = launch {
shared.collect { println("[B] terima: $it") }
}
delay(50) // pastikan collector siap
shared.emit("event-1")
shared.emit("event-2")
delay(50)
jobA.cancel()
jobB.cancel()
// 2. SharedFlow dengan replay buffer
println()
println("=== 2. SharedFlow dengan replay=2 ===")
val denganReplay = MutableSharedFlow<Int>(replay = 2)
denganReplay.emit(10)
denganReplay.emit(20)
denganReplay.emit(30) // nilai 10 diturunkan dari buffer replay
// Collector terlambat menerima replay buffer
println("collector terlambat menerima:")
denganReplay.take(2).collect { println(" - $it") }
}Output yang Dihasilkan:
=== 1. SharedFlow multicast ===
[A] terima: event-1
[B] terima: event-1
[A] terima: event-2
[B] terima: event-2
=== 2. SharedFlow dengan replay=2 ===
collector terlambat menerima:
- 20
- 30Mari kita telaah baris-baris kode di atas:
- Baris 7 (
MutableSharedFlow<String>(replay = 0)): membuat SharedFlow dengan replay=0. Nilai yang di-emit saat tidak ada collector hilang.replay=0cocok untuk event broker murni — hanya collector aktif yang menerima. - Baris 10-15 (dua collector A dan B): masing-masing
launchsebuah coroutine yang memanggilshared.collect { ... }.collectadalah suspend function yang menangguhkan coroutine sampai nilai tersedia. Karena SharedFlow multicast, kedua collector menerima setiap emit. - Baris 18-19 (
emit("event-1"),emit("event-2")): mengirim dua nilai. Karenareplay=0dan kedua collector sudah aktif (setelahdelay(50)), kedua nilai diterima oleh A dan B. Output menunjukkan keduanya mencetak “event-1” lalu “event-2”. - Baris 28 (
MutableSharedFlow<Int>(replay = 2)): membuat SharedFlow dengan replay=2. Dua nilai terakhir yang di-emit akan diingat dan dikirim ke collector baru saat mereka mulai mengumpulkan. - Baris 29-31 (
emit(10),emit(20),emit(30)): mengirim tiga nilai. Karena replay=2, hanya dua nilai terakhir yang disimpan di buffer replay. Nilai10tidak lagi tersimpan setelah30di-emit. - Baris 35 (
denganReplay.take(2).collect { ... }): collector baru memulai. Karena replay=2, collector menerima nilai20dan30(dua nilai terakhir) — bukan10. Operatortake(2)membatasi collect ke dua nilai pertama.
Konfigurasi SharedFlow #
Tiga parameter utama saat membuat MutableSharedFlow:
| Parameter | Default | Efek |
|---|---|---|
replay | 0 | Jumlah nilai terakhir yang di-replay ke collector baru |
extraBufferCapacity | 0 | Buffer tambahan untuk menampung nilai sebelum semua collector memproses |
onBufferOverflow | SUSPEND | Strategi saat buffer penuh |
Untuk emit() (suspend), SharedFlow dapat menangguhkan jika buffer penuh dan strategi adalah SUSPEND. Untuk tryEmit() (non-suspend), SharedFlow akan mengembalikan false jika tidak bisa mengirim — tidak menangguhkan.
val flow = MutableSharedFlow<String>(
replay = 1,
extraBufferCapacity = 4,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)Konfigurasi di atas: replay 1 nilai, buffer tambahan 4, dan drop nilai tertua saat buffer penuh — tidak pernah menangguhkan producer.
Hot vs Cold #
SharedFlow adalah hot — ia memancarkan nilai terlepas dari ada atau tidaknya collector. Kontras dengan Flow (cold) yang baru mulai menghasilkan nilai saat di-collect. Perbedaan ini memiliki implikasi praktis:
| Aspek | Cold (Flow) | Hot (SharedFlow) |
|---|---|---|
| Mulai | Saat di-collect | Saat di-emit |
| Eksekusi per collector | Ya — tiap collect menjalankan ulang | Tidak — semua collector berbagi |
| Replay | Tidak ada | Configurable |
| Cocok untuk | Sumber data yang bisa dimulai ulang | Event real-time, state |
Referensi Lanjutan #
- Flow
— panduan resmi
SharedFlowsebagai hot multicast stream - kotlinx.coroutines core
— API reference
kotlinx.coroutines.flow
Selanjutnya #
Setelah memahami konsep SharedFlow sebagai multicast event broker, bab StateFlow akan memperkenalkan varian khusus: state holder yang selalu memiliki nilai, dengan semantik conflation (hanya nilai terbaru yang dikirim ke collector baru).