package com.example.myapp.data
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Body
interface ApiService {
@GET("posts")
suspend fun getPosts(): List<Post>
@POST("posts")
suspend fun createPost(@Body post: Post): Post
}
class PostRepository(
private val apiService: ApiService,
private val postDao: PostDao
) {
private val _postsFlow = MutableStateFlow<List<Post>>(emptyList())
val postsFlow: StateFlow<List<Post>> = _postsFlow.asStateFlow()
suspend fun fetchPosts(): Result<List<Post>> = withContext(Dispatchers.IO) {
try {
val posts = apiService.getPosts()
postDao.insertAll(posts)
_postsFlow.value = posts
Result.success(posts)
} catch (e: Exception) {
// Fallback to cached data
val cachedPosts = postDao.getAllPosts()
_postsFlow.value = cachedPosts
Result.failure(e)
}
}
suspend fun createPost(post: Post): Result<Post> = withContext(Dispatchers.IO) {
try {
val created = apiService.createPost(post)
postDao.insert(created)
Result.success(created)
} catch (e: Exception) {
Result.failure(e)
}
}
fun observePosts(): Flow<List<Post>> = postDao.observePosts()
.flowOn(Dispatchers.IO)
.map { posts ->
posts.sortedByDescending { it.createdAt }
}
suspend fun syncPosts() = coroutineScope {
val local = async { postDao.getAllPosts() }
val remote = async { apiService.getPosts() }
val localPosts = local.await()
val remotePosts = remote.await()
// Merge and deduplicate
val merged = (localPosts + remotePosts)
.distinctBy { it.id }
.sortedByDescending { it.createdAt }
postDao.deleteAll()
postDao.insertAll(merged)
_postsFlow.value = merged
}
}
package com.example.myapp.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
class PostViewModel(
private val repository: PostRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(PostsUiState())
val uiState: StateFlow<PostsUiState> = _uiState.asStateFlow()
init {
loadPosts()
observePosts()
}
private fun observePosts() {
viewModelScope.launch {
repository.observePosts()
.catch { e ->
_uiState.update { it.copy(error = e.message) }
}
.collect { posts ->
_uiState.update { it.copy(posts = posts, isLoading = false) }
}
}
}
fun loadPosts() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
repository.fetchPosts()
.onSuccess { posts ->
_uiState.update { it.copy(posts = posts, isLoading = false) }
}
.onFailure { e ->
_uiState.update { it.copy(error = e.message, isLoading = false) }
}
}
}
fun createPost(title: String, body: String) {
viewModelScope.launch {
val post = Post(
id = 0,
title = title,
body = body,
author = "Current User",
createdAt = System.currentTimeMillis()
)
repository.createPost(post)
.onSuccess {
loadPosts()
}
.onFailure { e ->
_uiState.update { it.copy(error = e.message) }
}
}
}
}
data class PostsUiState(
val posts: List<Post> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
Kotlin coroutines provide structured concurrency for asynchronous programming. I use suspend functions to mark async code, and launch or async to start coroutines. viewModelScope and lifecycleScope tie coroutines to component lifecycles, cancelling automatically. Dispatchers control execution context—Dispatchers.IO for network/disk, Dispatchers.Main for UI updates, Dispatchers.Default for CPU work. withContext switches dispatchers. async/await enables parallel execution with awaitAll(). Flow provides reactive streams with operators like map, filter, and collect. StateFlow and SharedFlow share state across coroutines. Error handling uses try/catch or CoroutineExceptionHandler. Coroutines replace callbacks and RxJava with cleaner, sequential code.