Android: Architecture ViewModel, StateFlow & Room
ViewModel & StateFlow
// ViewModel — survives configuration changes (rotation)
// implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
class ArticlesViewModel(
private val repository: ArticleRepository
) : ViewModel() {
// UI state
private val _uiState = MutableStateFlow(ArticlesUiState())
val uiState: StateFlow<ArticlesUiState> = _uiState.asStateFlow()
init {
loadArticles()
}
fun loadArticles() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
val articles = repository.getArticles()
_uiState.update { it.copy(articles = articles, isLoading = false) }
} catch (e: Exception) {
_uiState.update { it.copy(error = e.message, isLoading = false) }
}
}
}
fun deleteArticle(id: Int) {
viewModelScope.launch {
repository.deleteArticle(id)
loadArticles()
}
}
}
data class ArticlesUiState(
val articles: List<Article> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
)
// In Composable
@Composable
fun ArticlesScreen(viewModel: ArticlesViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when {
uiState.isLoading -> CircularProgressIndicator()
uiState.error != null -> Text("Error: ${uiState.error}")
else -> ArticlesList(articles = uiState.articles)
}
}Room Database
// implementation("androidx.room:room-runtime:2.6.1")
// implementation("androidx.room:room-ktx:2.6.1")
// kapt("androidx.room:room-compiler:2.6.1")
@Entity(tableName = "articles")
data class ArticleEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val content: String,
val createdAt: Long = System.currentTimeMillis(),
)
@Dao
interface ArticleDao {
@Query("SELECT * FROM articles ORDER BY createdAt DESC")
fun getAllArticles(): Flow<List<ArticleEntity>> // Flow = reactive stream
@Query("SELECT * FROM articles WHERE id = :id")
suspend fun getArticleById(id: Int): ArticleEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(article: ArticleEntity)
@Update
suspend fun update(article: ArticleEntity)
@Delete
suspend fun delete(article: ArticleEntity)
@Query("DELETE FROM articles WHERE id = :id")
suspend fun deleteById(id: Int)
}
@Database(entities = [ArticleEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun articleDao(): ArticleDao
companion object {
@Volatile private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build().also { INSTANCE = it }
}
}
}
}Repository Pattern
class ArticleRepository(private val dao: ArticleDao, private val api: ArticleApi) {
// Combine local DB (offline-first) with network
val articles: Flow<List<Article>> = dao.getAllArticles()
.map { entities -> entities.map { it.toArticle() } }
suspend fun refreshArticles() {
val remoteArticles = api.getArticles()
dao.insertAll(remoteArticles.map { it.toEntity() })
}
suspend fun deleteArticle(id: Int) {
dao.deleteById(id)
}
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free