Room + Flow 完整教程(现代 Android 官方方案)
现代 Android 开发中:
Room + Flow + Compose/ViewModel
已经是官方推荐数据库架构。
真正强大的地方是:
数据变化 → UI 自动刷新
不需要:
notifyDataSetChanged()- 手动刷新
- 回调通知
这一篇会讲:
- Room 是什么
- Flow 为什么适合数据库
- Room + Flow 工作原理
- MVVM 实战
- Compose 配合
- 自动刷新机制
- 企业级最佳实践
一、Room 是什么?
Room 是 Android 官方数据库 ORM。
底层: SQLite
但 Room 帮你:
- 自动建表
- 自动 SQL 映射
- 自动线程检查
- 自动 Flow 更新
二、为什么 Room 要配合 Flow?
传统数据库:
kotlin
val list = dao.getUsers()
问题: 数据库变了,UI 不会自动更新
你得:手动刷新、LiveData、回调。很麻烦。
三、Flow 的核心优势
使用 Flow<List<User>> 后:
- 数据库变化
- Flow 自动重新发送数据
- UI 自动刷新
这就是:响应式数据库
四、添加依赖
kotlin
// Room
implementation "androidx.room:room-runtime:2.6.1"
ksp "androidx.room:room-compiler:2.6.1"
// Kotlin 扩展
implementation "androidx.room:room-ktx:2.6.1"
// Flow 协程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
五、Entity(数据表)
kotlin
@Entity(tableName = "user")
data class User(
@PrimaryKey
val id: Int,
val name: String,
val age: Int
)
六、DAO(最核心)
DAO = Data Access Object,数据库操作入口。
七、最重要的 Flow 查询
kotlin
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getUsers(): Flow<List<User>>
}
重点: 返回 Flow
八、为什么不用 suspend?
| suspend | Flow |
|---|---|
suspend fun getUsers() |
Flow<List<User>> |
| 只返回一次 | 持续监听数据库变化 |
九、Room 自动更新原理(重点)
这是 Room 最强大的地方。
Room 内部维护: InvalidationTracker
数据库变化
↓
通知 Flow
↓
Flow:重新 emit 最新数据
↓
UI:自动刷新
十、完整流程图(非常重要)
数据库 insert/update/delete
↓
Room InvalidationTracker
↓
Flow emit 新数据
↓
collectAsState 收到
↓
Compose 重组
↓
UI自动刷新
这就是:现代 Android 响应式数据库架构
十一、插入数据
kotlin
@Insert
suspend fun insert(user: User)
十二、删除数据
kotlin
@Delete
suspend fun delete(user: User)
十三、更新数据
kotlin
@Update
suspend fun update(user: User)
插入数据、删除、更新,为什么用suspend,不用Flow
Flow 本质是持续的数据流(Stream)
它适合:
bash
未来还会不断变化的数据
例如:
bash
数据库变化
Socket消息
搜索输入
传感器数据
聊天消息
插入、删除、更新操作并没有"流"
看:
bash
@Insert
suspend fun insert(user: User)
插入:
bash
执行一次
结束
它没有:
bash
后续数据
所以:Flow 没意义
如果 insert 用 Flow 会怎样?
理论上你可以:
bash
fun insert(user: User): Flow<Unit>
但:
bash
完全没必要
因为:
它只会:
bash
emit 一次
结束
这其实退化成:suspend
面试高频回答(建议背下来)
为什么 Room 查询用 Flow,而插入删除更新用 suspend?
标准答案:
bash
因为查询是持续变化的数据源,需要响应式监听数据库变化,所以适合使用 Flow。
而插入、删除、更新属于一次性操作,只需要执行并返回一次结果,因此使用 suspend 更合适。
十四、Database
kotlin
@Database(
entities = [User::class],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
十五、创建数据库
kotlin
val db = Room.databaseBuilder(
context,
AppDatabase::class.java,
"app.db"
).build()
十六、Repository(标准架构)
kotlin
class UserRepository(private val dao: UserDao) {
fun getUsers() = dao.getUsers()
suspend fun insert(user: User) {
dao.insert(user)
}
}
十七、为什么需要 Repository?
因为:ViewModel 不应该直接操作数据库,否则:
- 强耦合
- 不好测试
- 架构混乱
十八、ViewModel + StateFlow
现代标准方案。
kotlin
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
val users = repository.getUsers()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun addUser() {
viewModelScope.launch {
repository.insert(User(1, "Tom", 18))
}
}
}
十九、stateIn 是什么?
作用: Flow → StateFlow
因为:Compose 更适合 StateFlow。
二十、Compose 收集数据库
kotlin
@Composable
fun UserPage(vm: UserViewModel) {
val users by vm.users.collectAsState()
LazyColumn {
items(users) {
Text(it.name)
}
}
}
二十一、真正的神奇之处
调用 insert() 后,UI 自动刷新 ,完全不用 notifyDataSetChanged()。
二十二、为什么能自动刷新?
Room
↓
Flow
↓
collectAsState
↓
Recomposition
形成:响应式链路
二十三、collectAsStateWithLifecycle(推荐)
官方更推荐 collectAsStateWithLifecycle()
依赖
kotlin
implementation "androidx.lifecycle:lifecycle-runtime-compose"
使用
kotlin
val users by vm.users.collectAsStateWithLifecycle()
为什么推荐它? 自动处理生命周期,页面不可见自动停止收集。
二十四、Room 查询线程问题
suspend 查询
kotlin
@Query("SELECT * FROM user")
suspend fun getUsers(): List<User>
Room 自动后台线程执行。
Flow 查询
kotlin
Flow<List<User>>
也自动后台线程。
所以: 不需要 withContext(IO),这是 Room 帮你做好的。
二十五、Flow 为什么特别适合数据库?
因为数据库本质就是:持续变化的数据源
Flow 天生适合:观察变化
二十六、多个表联合查询
kotlin
data class UserWithArticles(
@Embedded
val user: User,
@Relation(
parentColumn = "id",
entityColumn = "userId"
)
val articles: List<Article>
)
二十七、事务查询
kotlin
@Transaction
@Query("SELECT * FROM user")
fun getUserWithArticles(): Flow<List<UserWithArticles>>
二十八、Flow 防抖搜索(经典)
DAO
kotlin
@Query("SELECT * FROM user WHERE name LIKE '%' || :keyword || '%'")
fun search(keyword: String): Flow<List<User>>
ViewModel
kotlin
val keyword = MutableStateFlow("")
val users = keyword
.debounce(300)
.flatMapLatest {
dao.search(it)
}
二十九、flatMapLatest 为什么重要?
因为输入变化时:
- 取消旧查询
- 启动新查询
避免: 搜索请求堆积
三十、Room + Paging3
现代大列表方案。
DAO
kotlin
@Query("SELECT * FROM user")
fun pagingSource(): PagingSource<Int, User>
Pager
kotlin
Pager(
config = PagingConfig(20)
) {
dao.pagingSource()
}.flow
Compose 分页
kotlin
val items = vm.users.collectAsLazyPagingItems()
三十一、Room + Flow + Compose 真正完整链路
这是现代 Android 最核心架构。
执行流程:
Room(SQLite)
↓
Flow
↓
Repository
↓
ViewModel
↓
StateFlow
↓
Compose collectAsState
↓
Recomposition
↓
UI刷新
三十二、Room 常见错误
| 错误 | 说明 |
|---|---|
| 在主线程操作数据库 | 禁止 allowMainThreadQueries() |
| UI 直接操作 DAO | 应该:Compose → ViewModel → Repository → DAO |
| Flow collect 泄漏 | flow.collect 没有生命周期 |
正确: collectAsStateWithLifecycle()
三十三、Room vs LiveData
现在 Flow 已经基本替代 LiveData,因为:
| Flow | LiveData |
|---|---|
| 更强 | 受限 |
| 支持操作符 | 功能单一 |
| 协程统一 | 非协程 |
| Kotlin 原生 | Android 特定 |
三十四、Room + Flow 面试题
1. Room 为什么支持自动刷新?
因为:InvalidationTracker
2. Flow 和 suspend 查询区别?
| suspend | Flow |
|---|---|
| 一次返回 | 持续监听 |
| 单次数据 | 数据流 |
3. Room 查询为什么不卡主线程?
Room 自动线程调度。
4. 为什么推荐 StateFlow?
因为:Compose 状态驱动
5. Flow 为什么适合数据库?
因为数据库是持续变化的数据源
三十五、企业级最佳实践(非常重要)
标准架构:
Compose UI
↓
ViewModel
↓
StateFlow
↓
Repository
↓
Room DAO
↓
SQLite
三十六、真正理解 Room + Flow
以前 Android:
数据库变化
↓
手动通知UI
↓
RecyclerView刷新
现代 Android:
数据库变化
↓
Flow自动发射
↓
Compose自动重组
↓
UI自动刷新
三十七、真正的大脑模型(最重要)
看到 Flow<List<User>>,脑子里自动出现:
数据库监听器
↓
数据变化自动emit
↓
UI自动刷新
看到 collectAsState(),自动想到:
Flow → State → Compose重组
看到 stateIn(viewModelScope),自动想到:
冷Flow → 热StateFlow → UI状态共享
三十八、最后一句(现代 Android 的本质)
现在 Android 官方整个方向:
已经从:命令式UI
变成:响应式状态驱动
核心链路:
数据库
↓
Flow
↓
StateFlow
↓
Compose
↓
自动UI刷新
这就是现代 Android 架构真正的核心。