DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Flow/StateFlow Practical Patterns Collection — combine/flatMap/debounce/retry

What You'll Learn

Flow/StateFlow実践パターン(combine、flatMapLatest、debounce、retry、callbackFlow、テスト)を解説します。


combine: Combining Multiple Flows

@HiltViewModel
class DashboardViewModel @Inject constructor(
    userRepository: UserRepository,
    orderRepository: OrderRepository,
    notificationRepository: NotificationRepository
) : ViewModel() {

    val dashboardState = combine(
        userRepository.getUser(),
        orderRepository.getRecentOrders(),
        notificationRepository.getUnreadCount()
    ) { user, orders, unreadCount ->
        DashboardState(user, orders, unreadCount)
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), DashboardState())
}
Enter fullscreen mode Exit fullscreen mode

flatMapLatest: Search

@HiltViewModel
class SearchViewModel @Inject constructor(
    private val repository: SearchRepository
) : ViewModel() {
    private val _query = MutableStateFlow("")

    val results = _query
        .debounce(300)  // 300ms待ってから検索
        .distinctUntilChanged()
        .flatMapLatest { query ->
            if (query.isBlank()) flowOf(emptyList())
            else repository.search(query)
        }
        .catch { emit(emptyList()) }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    fun onQueryChange(query: String) { _query.value = query }
}
Enter fullscreen mode Exit fullscreen mode

retry: Error Retry

fun getDataWithRetry(): Flow<List<Item>> = flow {
    emit(api.getItems())
}.retry(3) { cause ->
    cause is IOException && run {
        delay(1000)
        true
    }
}.catch { emit(emptyList()) }

// exponential backoff
fun <T> Flow<T>.retryWithBackoff(
    maxRetries: Int = 3,
    initialDelay: Long = 1000
): Flow<T> = retryWhen { cause, attempt ->
    if (attempt < maxRetries && cause is IOException) {
        delay(initialDelay * (1 shl attempt.toInt()))
        true
    } else false
}
Enter fullscreen mode Exit fullscreen mode

callbackFlow: Callback to Flow Conversion

fun locationUpdates(context: Context): Flow<Location> = callbackFlow {
    val client = LocationServices.getFusedLocationProviderClient(context)
    val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000).build()

    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            result.lastLocation?.let { trySend(it) }
        }
    }

    client.requestLocationUpdates(request, callback, Looper.getMainLooper())
    awaitClose { client.removeLocationUpdates(callback) }
}
Enter fullscreen mode Exit fullscreen mode

stateIn vs shareIn

// stateIn: 最新値を保持(UI状態向け)
val uiState = flow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),  // 5秒キャッシュ
    initialValue = UiState.Loading
)

// shareIn: イベントストリーム(通知向け)
val events = flow.shareIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    replay = 0  // 新しいサブスクライバに過去のイベントを送らない
)
Enter fullscreen mode Exit fullscreen mode

Testing (Turbine)

@Test
fun searchReturnsResults() = runTest {
    val viewModel = SearchViewModel(FakeSearchRepository())

    viewModel.results.test {
        assertEquals(emptyList<SearchResult>(), awaitItem()) // 初期値

        viewModel.onQueryChange("kotlin")
        advanceTimeBy(300) // debounce待ち

        val results = awaitItem()
        assertTrue(results.isNotEmpty())
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

パターン 用途
combine Combine multiple flows
flatMapLatest Process latest only
debounce Wait for input
retry Error retry
callbackFlow Callback conversion
stateIn Share UI state
  • combineでIntegrate multiple data sources
  • debounce + flatMapLatestでSearch optimization
  • callbackFlowでLegacy API to Flow
  • WhileSubscribed(5000)でBackground optimization

8種類のAndroidAppTemplates(FlowPre-designed)を公開しています。

Template ListGumroad

Related Articles:


Ready-Made Android App Templates

8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.

Browse templatesGumroad

Top comments (0)