kotlinadvanced

StateFlow and SharedFlow Usage Patterns

Use hot flows for state management: MutableStateFlow for UI state, SharedFlow for events and broadcasting.

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

// StateFlow: holds current state, emits to new collectors
class CounterViewModel {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun increment() { _count.value++ }
    fun decrement() { _count.value-- }

    suspend fun loadData() {
        _uiState.value = UiState.Loading
        delay(1000) // simulate API call
        _uiState.value = UiState.Success("Data loaded at ${System.currentTimeMillis()}")
    }
}

sealed class UiState {
    object Idle : UiState()
    object Loading : UiState()
    data class Success(val data: String) : UiState()
    data class Error(val message: String) : UiState()
}

// SharedFlow: event broadcasting, no replay by default
class EventBus {
    private val _events = MutableSharedFlow<AppEvent>(
        replay = 0,
        extraBufferCapacity = 64
    )
    val events: SharedFlow<AppEvent> = _events.asSharedFlow()

    suspend fun emit(event: AppEvent) {
        _events.emit(event)
    }
}

sealed class AppEvent {
    data class UserLoggedIn(val userId: String) : AppEvent()
    data class UserLoggedOut(val userId: String) : AppEvent()
    data class ItemPurchased(val itemId: String, val amount: Double) : AppEvent()
    data class ErrorOccurred(val message: String) : AppEvent()
}

fun main() = runBlocking {
    // StateFlow demo
    val viewModel = CounterViewModel()

    // Collector 1
    val collector1 = launch {
        viewModel.count.collect { println("Collector1: count = $it") }
    }

    delay(100)
    viewModel.increment()
    viewModel.increment()
    viewModel.increment()
    delay(100)

    // New collector gets current value immediately
    val collector2 = launch {
        viewModel.count.take(1).collect { println("Collector2 (late): count = $it") }
    }
    delay(100)
    collector1.cancel()
    collector2.cancel()

    // SharedFlow: event bus
    val eventBus = EventBus()
    val subscriber = launch {
        eventBus.events.collect { event ->
            when (event) {
                is AppEvent.UserLoggedIn -> println("Login: ${event.userId}")
                is AppEvent.UserLoggedOut -> println("Logout: ${event.userId}")
                is AppEvent.ItemPurchased -> println("Purchase: ${event.itemId} for $${event.amount}")
                is AppEvent.ErrorOccurred -> println("Error: ${event.message}")
            }
        }
    }

    delay(100)
    eventBus.emit(AppEvent.UserLoggedIn("user-123"))
    eventBus.emit(AppEvent.ItemPurchased("item-456", 29.99))
    eventBus.emit(AppEvent.UserLoggedOut("user-123"))
    delay(100)
    subscriber.cancel()

    println("Done")
}

Use Cases

  • UI state management in Android/KMP apps
  • Event broadcasting across application layers
  • Real-time data updates and subscriptions

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.