安卓 开发中 Room 是什么

方木先生
    分享互动规则

    Room 是什么


    目录

    1. Room 是什么
    2. 本地存储选型对比
    3. 核心架构:三个必学的概念
    4. 环境搭建
    5. Entity — 定义数据表
    6. DAO — 定义数据操作
    7. Database — 组装数据库
    8. 基础 CRUD 操作
    9. Room + Flow:实时数据流
    10. Room + ViewModel:完整闭环
    11. 表关系:一对多 & 多对多
    12. 数据库迁移
    13. Flutter 开发者速查对照

    1. Room 是什么

    Room 是 Google Jetpack 提供的 SQLite ORM 框架,它在原始 SQLite API 上加了一层抽象,让你用 Kotlin 注解代替手写 SQL 模板代码。

    用一句话类比:Room = Android 版的 sqflite + drift(原 moor)的结合体——既有 Flutter drift 的类型安全和代码生成,又有 sqflite 的 SQLite 底层能力。

    Room 解决了什么问题?

    原始 SQLite 的痛点Room 的解法
    大量模板代码(ContentValuesCursor注解驱动,编译期自动生成代码
    运行时 SQL 错误编译期检查 SQL 语法
    主线程操作数据库导致 ANR强制要求异步,配合协程
    数据变化无法实时感知原生支持返回 Flow,数据变化自动推送
    跨版本升级复杂内置 Migration 迁移机制

    2. 本地存储选型对比

    方案适用场景Flutter 类比
    Room结构化数据、复杂查询、关联表drift / sqflite
    DataStore Preferences键值对设置项(用户偏好)shared_preferences
    DataStore Proto类型安全的键值对hive(基础类型)
    文件存储大文件、图片、音频path_provider + dart:io
    EncryptedSharedPreferences加密键值对(token、密钥)flutter_secure_storage

    选型原则:需要查询、排序、关联 → Room;简单设置项 → DataStore;敏感信息 → EncryptedSharedPreferences。


    3. 核心架构:三个必学的概念

    ┌─────────────────────────────────────────────┐
    │              Your Application               │
    │                                             │
    │   ViewModel / Repository                   │
    │        │                                   │
    │        ▼                                   │
    │  ┌─────────────┐                           │
    │  │     DAO     │  ← 你只需要写这个接口       │
    │  │  (接口定义)  │                           │
    │  └──────┬──────┘                           │
    │         │  Room 编译期自动生成实现            │
    │         ▼                                   │
    │  ┌─────────────┐                           │
    │  │  Database   │  ← 数据库单例              │
    │  │  (抽象类)   │                           │
    │  └──────┬──────┘                           │
    │         │                                   │
    │         ▼                                   │
    │  ┌─────────────┐                           │
    │  │   Entity    │  ← 一个 Entity = 一张表    │
    │  │  (数据类)   │                           │
    │  └─────────────┘                           │
    └─────────────────────────────────────────────┘
    
    概念职责Flutter drift 类比
    @Entity定义数据表结构(字段、主键、索引)class Tasks extends Table
    @Dao定义增删改查接口(SQL 在这里写)abstract class TasksDao
    @Database组装数据库(关联 Entity 和 DAO)AppDatabase extends _$AppDatabase

    4. 环境搭建

    build.gradle.kts

    plugins {
        id("com.google.devtools.ksp") version "1.9.22-1.0.17" // KSP 代码生成
    }
    
    dependencies {
        val roomVersion = "2.6.1"
    
        implementation("androidx.room:room-runtime:$roomVersion")
        implementation("androidx.room:room-ktx:$roomVersion")   // 协程 + Flow 支持
        ksp("androidx.room:room-compiler:$roomVersion")          // 编译期代码生成
    
        // 可选:Room 测试支持
        testImplementation("androidx.room:room-testing:$roomVersion")
    }
    

    注意:必须用 ksp 而不是 kapt,KSP 编译速度快 2 倍以上。


    5. Entity — 定义数据表

    @Entity 注解的 Kotlin 数据类 = 一张数据库表,每个字段 = 一列。

    基础 Entity

    @Entity(tableName = "users")           // 指定表名,不写默认用类名小写
    data class UserEntity(
    
        @PrimaryKey(autoGenerate = true)   // 自增主键,类比 SQL: INTEGER PRIMARY KEY AUTOINCREMENT
        val id: Int = 0,
    
        @ColumnInfo(name = "display_name") // 自定义列名(不写则用字段名)
        val name: String,
    
        val email: String,
    
        val avatarUrl: String? = null,     // 可空字段 → 列允许 NULL
    
        val createdAt: Long = System.currentTimeMillis()
    )
    

    带索引和唯一约束

    @Entity(
        tableName = "articles",
        indices = [
            Index(value = ["slug"], unique = true),  // slug 唯一索引
            Index(value = ["author_id"]),             // author_id 普通索引(加速查询)
            Index(value = ["category", "published_at"]) // 联合索引
        ]
    )
    data class ArticleEntity(
        @PrimaryKey val id: String,         // 字符串主键(如 UUID)
        val title: String,
        val slug: String,                   // 唯一,不能重复
        @ColumnInfo(name = "author_id")
        val authorId: Int,
        val category: String,
        @ColumnInfo(name = "published_at")
        val publishedAt: Long,
        val content: String,
        val isBookmarked: Boolean = false
    )
    

    复合主键

    @Entity(
        tableName = "user_roles",
        primaryKeys = ["user_id", "role_id"]   // 联合主键
    )
    data class UserRoleEntity(
        @ColumnInfo(name = "user_id") val userId: Int,
        @ColumnInfo(name = "role_id") val roleId: Int,
        val assignedAt: Long = System.currentTimeMillis()
    )
    

    类型转换器(存储复杂类型)

    Room 原生只支持基础类型。存 ListDate、自定义对象需要 TypeConverter

    // 定义转换器
    class Converters {
        // List<String> ↔ JSON 字符串
        @TypeConverter
        fun fromStringList(value: List<String>): String =
            Gson().toJson(value)
    
        @TypeConverter
        fun toStringList(value: String): List<String> =
            Gson().fromJson(value, object : TypeToken<List<String>>() {}.type)
    
        // Date ↔ Long 时间戳
        @TypeConverter
        fun fromDate(date: Date?): Long? = date?.time
    
        @TypeConverter
        fun toDate(timestamp: Long?): Date? =
            timestamp?.let { Date(it) }
    }
    
    // 在 Database 类上声明使用
    @TypeConverters(Converters::class)
    @Database(...)
    abstract class AppDatabase : RoomDatabase() { ... }
    

    6. DAO — 定义数据操作

    DAO(Data Access Object)是一个 interfaceabstract class,用注解描述 SQL,Room 编译期自动生成实现

    基础 CRUD

    @Dao
    interface UserDao {
    
        // ── INSERT ─────────────────────────────────────────
        @Insert(onConflict = OnConflictStrategy.REPLACE)  // 冲突时替换(类比 upsert)
        suspend fun insert(user: UserEntity): Long         // 返回新行的 rowId
    
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        suspend fun insertAll(users: List<UserEntity>): List<Long>
    
        // ── UPDATE ─────────────────────────────────────────
        @Update
        suspend fun update(user: UserEntity): Int          // 返回影响行数
    
        // ── DELETE ─────────────────────────────────────────
        @Delete
        suspend fun delete(user: UserEntity): Int
    
        @Query("DELETE FROM users WHERE id = :userId")
        suspend fun deleteById(userId: Int): Int
    
        @Query("DELETE FROM users")
        suspend fun deleteAll()
    
        // ── QUERY ──────────────────────────────────────────
        @Query("SELECT * FROM users ORDER BY display_name ASC")
        fun getAllUsers(): Flow<List<UserEntity>>           // Flow:数据变化自动推送
    
        @Query("SELECT * FROM users WHERE id = :id")
        suspend fun getUserById(id: Int): UserEntity?      // suspend:一次性查询
    
        @Query("SELECT * FROM users WHERE email = :email LIMIT 1")
        suspend fun getUserByEmail(email: String): UserEntity?
    
        // 模糊搜索
        @Query("SELECT * FROM users WHERE display_name LIKE '%' || :query || '%'")
        fun searchUsers(query: String): Flow<List<UserEntity>>
    
        // 分页查询
        @Query("SELECT * FROM users ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
        suspend fun getUsersPaged(limit: Int, offset: Int): List<UserEntity>
    }
    

    高级查询技巧

    @Dao
    interface ArticleDao {
    
        // 返回部分字段(投影查询),用数据类接收
        @Query("SELECT id, title, published_at FROM articles WHERE category = :cat")
        fun getArticleSummaries(cat: String): Flow<List<ArticleSummary>>
    
        // 多条件动态查询
        @Query("""
            SELECT * FROM articles
            WHERE (:category IS NULL OR category = :category)
              AND (:authorId IS NULL OR author_id = :authorId)
              AND published_at >= :fromTime
            ORDER BY published_at DESC
        """)
        fun filterArticles(
            category: String?,
            authorId: Int?,
            fromTime: Long
        ): Flow<List<ArticleEntity>>
    
        // Upsert(Room 2.5.0+)
        @Upsert
        suspend fun upsert(article: ArticleEntity)
    
        // 批量 Upsert
        @Upsert
        suspend fun upsertAll(articles: List<ArticleEntity>)
    
        // 事务(多个操作原子执行)
        @Transaction
        suspend fun replaceAll(articles: List<ArticleEntity>) {
            deleteAll()
            insertAll(articles)
        }
    
        @Insert
        suspend fun insertAll(articles: List<ArticleEntity>)
    
        @Query("DELETE FROM articles")
        suspend fun deleteAll()
    }
    
    // 投影查询的接收数据类(不需要 @Entity)
    data class ArticleSummary(
        val id: String,
        val title: String,
        @ColumnInfo(name = "published_at") val publishedAt: Long
    )
    

    7. Database — 组装数据库

    @Database(
        entities = [
            UserEntity::class,
            ArticleEntity::class,
            UserRoleEntity::class,
        ],
        version = 1,                     // 数据库版本,升级时递增
        exportSchema = true              // 导出 schema.json 用于迁移验证(推荐 true)
    )
    @TypeConverters(Converters::class)
    abstract class AppDatabase : RoomDatabase() {
    
        // 每个 DAO 定义一个抽象方法
        abstract fun userDao(): UserDao
        abstract fun articleDao(): ArticleDao
    
        companion object {
            @Volatile
            private var INSTANCE: AppDatabase? = null
    
            fun getInstance(context: Context): AppDatabase {
                // 双重检查锁定单例模式
                return INSTANCE ?: synchronized(this) {
                    Room.databaseBuilder(
                        context.applicationContext,
                        AppDatabase::class.java,
                        "app_database.db"              // 数据库文件名
                    )
                    .fallbackToDestructiveMigration() // 开发期用:版本不匹配时清库重建
                    // .addMigrations(MIGRATION_1_2)  // 生产环境用迁移脚本
                    .build()
                    .also { INSTANCE = it }
                }
            }
        }
    }
    

    生产建议:用 Hilt 依赖注入管理 Database 单例,而不是手写 companion object。

    Hilt 注入版本

    @Module
    @InstallIn(SingletonComponent::class)
    object DatabaseModule {
    
        @Provides
        @Singleton
        fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
            Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
                .build()
    
        @Provides
        fun provideUserDao(db: AppDatabase): UserDao = db.userDao()
    
        @Provides
        fun provideArticleDao(db: AppDatabase): ArticleDao = db.articleDao()
    }
    

    8. 基础 CRUD 操作

    Repository 层封装

    class UserRepository @Inject constructor(
        private val userDao: UserDao
    ) {
        // 查询(Flow,实时)
        val allUsers: Flow<List<UserEntity>> = userDao.getAllUsers()
    
        // 查询(单次)
        suspend fun getUserById(id: Int): UserEntity? = userDao.getUserById(id)
    
        // 新增
        suspend fun addUser(name: String, email: String): Long {
            val entity = UserEntity(name = name, email = email)
            return userDao.insert(entity)
        }
    
        // 修改
        suspend fun updateUser(user: UserEntity) = userDao.update(user)
    
        // 删除
        suspend fun deleteUser(user: UserEntity) = userDao.delete(user)
    
        // 搜索
        fun searchUsers(query: String): Flow<List<UserEntity>> =
            userDao.searchUsers(query)
    }
    

    在 ViewModel 中调用

    @HiltViewModel
    class UserViewModel @Inject constructor(
        private val repository: UserRepository
    ) : ViewModel() {
    
        val users: StateFlow<List<UserEntity>> = repository.allUsers
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = emptyList()
            )
    
        fun addUser(name: String, email: String) {
            viewModelScope.launch {
                repository.addUser(name, email)
                // users Flow 会自动更新,无需手动刷新!
            }
        }
    
        fun deleteUser(user: UserEntity) {
            viewModelScope.launch {
                repository.deleteUser(user)
            }
        }
    }
    

    在 Compose UI 中展示

    @Composable
    fun UserListScreen(viewModel: UserViewModel = hiltViewModel()) {
        val users by viewModel.users.collectAsStateWithLifecycle()
    
        LazyColumn {
            items(users, key = { it.id }) { user ->
                UserItem(
                    user = user,
                    onDelete = { viewModel.deleteUser(user) }
                )
            }
        }
    }
    

    9. Room + Flow:实时数据流

    这是 Room 最强大的特性:查询返回 Flow,数据库内容变化时 Flow 自动发射新数据

    数据库写入操作
         │
         ▼
      Room 检测到表数据变化
         │
         ▼
      Flow 自动 emit 新数据
         │
         ▼
      ViewModel 的 StateFlow 更新
         │
         ▼
      Compose UI 自动重组
    
    @Dao
    interface NoteDao {
        // 返回 Flow:Room 会在 notes 表数据变化时自动重新查询并 emit
        @Query("SELECT * FROM notes ORDER BY updated_at DESC")
        fun getAllNotes(): Flow<List<NoteEntity>>
    
        // 配合 combine 合并多个查询结果
        @Query("SELECT COUNT(*) FROM notes WHERE is_pinned = 1")
        fun getPinnedCount(): Flow<Int>
    }
    
    // ViewModel 中合并两个 Flow
    val uiState: StateFlow<NoteUiState> = combine(
        noteDao.getAllNotes(),
        noteDao.getPinnedCount()
    ) { notes, pinnedCount ->
        NoteUiState(notes = notes, pinnedCount = pinnedCount)
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), NoteUiState())
    

    10. Room + ViewModel:完整闭环

    Todo 应用为例,展示从数据库到 UI 的完整链路:

    Entity

    @Entity(tableName = "todos")
    data class TodoEntity(
        @PrimaryKey(autoGenerate = true) val id: Int = 0,
        val title: String,
        val description: String = "",
        val isCompleted: Boolean = false,
        val priority: Int = 0,             // 0=低 1=中 2=高
        val createdAt: Long = System.currentTimeMillis()
    )
    

    DAO

    @Dao
    interface TodoDao {
        @Query("SELECT * FROM todos ORDER BY priority DESC, created_at DESC")
        fun getAllTodos(): Flow<List<TodoEntity>>
    
        @Query("SELECT * FROM todos WHERE is_completed = :completed")
        fun getTodosByStatus(completed: Boolean): Flow<List<TodoEntity>>
    
        @Upsert
        suspend fun upsert(todo: TodoEntity)
    
        @Delete
        suspend fun delete(todo: TodoEntity)
    
        @Query("UPDATE todos SET is_completed = :completed WHERE id = :id")
        suspend fun updateStatus(id: Int, completed: Boolean)
    
        @Query("SELECT COUNT(*) FROM todos WHERE is_completed = 0")
        fun getPendingCount(): Flow<Int>
    }
    

    Repository

    class TodoRepository @Inject constructor(private val dao: TodoDao) {
        val allTodos: Flow<List<TodoEntity>>      = dao.getAllTodos()
        val pendingCount: Flow<Int>               = dao.getPendingCount()
    
        suspend fun addTodo(title: String, priority: Int) =
            dao.upsert(TodoEntity(title = title, priority = priority))
    
        suspend fun toggleTodo(todo: TodoEntity) =
            dao.updateStatus(todo.id, !todo.isCompleted)
    
        suspend fun deleteTodo(todo: TodoEntity) = dao.delete(todo)
    }
    

    ViewModel

    @HiltViewModel
    class TodoViewModel @Inject constructor(
        private val repository: TodoRepository
    ) : ViewModel() {
    
        data class UiState(
            val todos: List<TodoEntity> = emptyList(),
            val pendingCount: Int = 0
        )
    
        val uiState: StateFlow<UiState> = combine(
            repository.allTodos,
            repository.pendingCount
        ) { todos, count ->
            UiState(todos = todos, pendingCount = count)
        }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState())
    
        fun addTodo(title: String, priority: Int = 1) {
            if (title.isBlank()) return
            viewModelScope.launch { repository.addTodo(title, priority) }
        }
    
        fun toggleTodo(todo: TodoEntity) {
            viewModelScope.launch { repository.toggleTodo(todo) }
        }
    
        fun deleteTodo(todo: TodoEntity) {
            viewModelScope.launch { repository.deleteTodo(todo) }
        }
    }
    

    Compose UI

    @Composable
    fun TodoScreen(viewModel: TodoViewModel = hiltViewModel()) {
        val state by viewModel.uiState.collectAsStateWithLifecycle()
        var inputText by remember { mutableStateOf("") }
    
        Scaffold(
            topBar = {
                TopAppBar(title = { Text("待办 (${state.pendingCount} 未完成)") })
            }
        ) { padding ->
            Column(modifier = Modifier.padding(padding)) {
                // 输入框
                Row(
                    modifier = Modifier.padding(16.dp),
                    horizontalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    OutlinedTextField(
                        value = inputText,
                        onValueChange = { inputText = it },
                        placeholder = { Text("新建待办...") },
                        modifier = Modifier.weight(1f),
                        singleLine = true
                    )
                    Button(onClick = {
                        viewModel.addTodo(inputText)
                        inputText = ""
                    }) { Text("添加") }
                }
    
                // 列表
                LazyColumn {
                    items(state.todos, key = { it.id }) { todo ->
                        TodoItem(
                            todo = todo,
                            onToggle = { viewModel.toggleTodo(todo) },
                            onDelete = { viewModel.deleteTodo(todo) }
                        )
                    }
                }
            }
        }
    }
    
    @Composable
    fun TodoItem(
        todo: TodoEntity,
        onToggle: () -> Unit,
        onDelete: () -> Unit
    ) {
        ListItem(
            headlineContent = {
                Text(
                    text = todo.title,
                    textDecoration = if (todo.isCompleted)
                        TextDecoration.LineThrough else TextDecoration.None
                )
            },
            leadingContent = {
                Checkbox(checked = todo.isCompleted, onCheckedChange = { onToggle() })
            },
            trailingContent = {
                IconButton(onClick = onDelete) {
                    Icon(Icons.Default.Delete, contentDescription = "删除")
                }
            }
        )
    }
    

    11. 表关系:一对多 & 多对多

    一对多(One-to-Many)

    // 一个 User 有多个 Post
    @Entity(tableName = "posts")
    data class PostEntity(
        @PrimaryKey(autoGenerate = true) val id: Int = 0,
        @ColumnInfo(name = "user_id") val userId: Int,  // 外键
        val title: String,
        val content: String
    )
    
    // 关联查询结果
    data class UserWithPosts(
        @Embedded val user: UserEntity,           // @Embedded:展开嵌套对象的字段
        @Relation(
            parentColumn = "id",                  // UserEntity 的主键
            entityColumn = "user_id"              // PostEntity 的外键
        )
        val posts: List<PostEntity>
    )
    
    // DAO 查询
    @Dao
    interface UserWithPostsDao {
        @Transaction                              // 关联查询必须加 @Transaction
        @Query("SELECT * FROM users WHERE id = :userId")
        fun getUserWithPosts(userId: Int): Flow<UserWithPosts?>
    
        @Transaction
        @Query("SELECT * FROM users")
        fun getAllUsersWithPosts(): Flow<List<UserWithPosts>>
    }
    

    多对多(Many-to-Many)

    // 学生 & 课程:一个学生选多门课,一门课有多个学生
    @Entity(tableName = "students")
    data class StudentEntity(@PrimaryKey val id: Int, val name: String)
    
    @Entity(tableName = "courses")
    data class CourseEntity(@PrimaryKey val id: Int, val title: String)
    
    // 中间关联表
    @Entity(
        tableName = "student_course",
        primaryKeys = ["student_id", "course_id"]
    )
    data class StudentCourseCrossRef(
        @ColumnInfo(name = "student_id") val studentId: Int,
        @ColumnInfo(name = "course_id")  val courseId: Int
    )
    
    // 关联结果(学生 + 所选课程)
    data class StudentWithCourses(
        @Embedded val student: StudentEntity,
        @Relation(
            parentColumn = "id",
            entityColumn = "id",
            associateBy = Junction(
                value = StudentCourseCrossRef::class,
                parentColumn = "student_id",
                entityColumn = "course_id"
            )
        )
        val courses: List<CourseEntity>
    )
    
    @Dao
    interface StudentDao {
        @Transaction
        @Query("SELECT * FROM students WHERE id = :studentId")
        fun getStudentWithCourses(studentId: Int): Flow<StudentWithCourses?>
    }
    

    12. 数据库迁移

    每次修改 Entity(加字段、改类型)都必须升级版本并提供迁移脚本:

    // 版本 1 → 版本 2:users 表增加 phone 字段
    val MIGRATION_1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL(
                "ALTER TABLE users ADD COLUMN phone TEXT"
            )
        }
    }
    
    // 版本 2 → 版本 3:新增 tags 表
    val MIGRATION_2_3 = object : Migration(2, 3) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("""
                CREATE TABLE IF NOT EXISTS tags (
                    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                    name TEXT NOT NULL,
                    color TEXT NOT NULL DEFAULT '#000000'
                )
            """)
        }
    }
    
    // 注册迁移脚本
    Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
        .build()
    

    开发期快捷方式.fallbackToDestructiveMigration() 会在版本不匹配时自动删库重建,仅用于开发阶段,生产环境必须写 Migration。


    13. Flutter 开发者速查对照

    概念Flutter (drift)Android Room
    表定义class Tasks extends Table@Entity data class TaskEntity
    主键IntColumn get id => integer().autoIncrement()()@PrimaryKey(autoGenerate = true) val id: Int
    DAOabstract class TasksDao@Dao interface TaskDao
    插入into(tasks).insert(task)@Insert suspend fun insert(task: TaskEntity)
    查询全部select(tasks).watch()@Query("SELECT * FROM tasks") fun getAll(): Flow<List<TaskEntity>>
    条件查询(select(tasks)..where((t) => t.done.equals(false))).watch()@Query("SELECT * FROM tasks WHERE is_done = 0") fun getPending(): Flow<...>
    实时更新Stream<List<Task>>Flow<List<TaskEntity>>
    数据库类@DriftDatabase(tables: [Tasks])@Database(entities = [TaskEntity::class])
    迁移MigrationStrategyMigration(from, to)
    类型转换TypeConverter@TypeConverter
    事务transaction(() async { ... })@Transaction suspend fun ...

    快速参考:常用注解速查

    // Entity 相关
    @Entity(tableName = "table_name")        // 声明数据表
    @PrimaryKey(autoGenerate = true)         // 自增主键
    @ColumnInfo(name = "column_name")        // 自定义列名
    @Ignore                                  // 忽略此字段(不映射到数据库)
    @Embedded                                // 嵌套对象展开到同一张表
    @Relation(parentColumn, entityColumn)    // 声明表关系
    
    // DAO 相关
    @Dao                                     // 声明 DAO 接口
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    @Update
    @Delete
    @Upsert                                  // Room 2.5.0+
    @Query("SELECT ...")                     // 自定义 SQL
    @Transaction                             // 原子事务
    
    // Database 相关
    @Database(entities = [...], version = N)
    @TypeConverters(Converters::class)
    

    推荐学习路径UserEntity + UserDao + AppDatabase 三件套跑通 → 加入 Flow 实时监听 → 接入 ViewModel → 理解表关系 → 学习迁移机制。整个过程和 Flutter drift 的心智模型几乎一致,上手非常快。

    评论 0

    支持 @用户名 提醒对方(需为站内已注册用户名);回复仅支持一层楼中楼。

    登录后发表评论、回复与 @ 提及。

    举报

    举报会匿名发送给管理员审核。

    • 暂无评论,来发表第一条。

    码谱 · The Digital Atelier · 技术内容社区