modern-app-patterns

Coroutines + Flow Basics

Use structured concurrency and cold streams for async work.

Snippets

suspend fun <T> retry(times: Int, block: suspend () -> T): T {
  repeat(times - 1) { runCatching { return block() } }
  return block()
}
fun <T> Flow<T>.asState(initial: T): StateFlow<T> =
  this.stateIn(scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate), started = SharingStarted.WhileSubscribed(5_000), initialValue = initial)
viewModelScope.launch { withContext(Dispatchers.IO) { repo.sync() } }

Notes


Live end-to-end example (copy/paste)

Flow from repository → ViewModel StateFlow → Compose UI.

// data/TickerRepo.kt
interface TickerRepo { fun stream(): Flow<Int> }
class FakeTickerRepo : TickerRepo {
  override fun stream(): Flow<Int> = flow {
    var i = 0
    while (true) { emit(i++); delay(1000) }
  }
}
// ui/TickerViewModel.kt
@HiltViewModel
class TickerViewModel @Inject constructor(repo: TickerRepo): ViewModel() {
  val count: StateFlow<Int> = repo.stream()
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), 0)
}
// ui/TickerScreen.kt
@Composable
fun TickerScreen(vm: TickerViewModel = hiltViewModel()) {
  val c by vm.count.collectAsStateWithLifecycle()
  Text("Tick: $c")
}

Notes

Sandbox copy map

Paste into Android Studio project (see sandboxes/android-compose):