You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose不使用ViewModel构建Room应用崩溃求助

问题描述

为学习目的,我在Jetpack Compose中不使用ViewModel构建了一个简易Room应用,但应用出现崩溃情况。以下是相关代码,恳请提供解决建议。

Room数据库代码

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val title: String,
    val content: String
)

@Dao
interface NoteDao {
    @Query("SELECT * FROM notes")
    fun getAllNotes(): List<Note>

    @Insert
    fun insert(note: Note): Long
}

@Database(entities = [Note::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao
}

MainActivity代码

class MainActivity : ComponentActivity() {
    private lateinit var db: AppDatabase
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "note-database"
        ).build()
        // ...
        NotesApp(db.noteDao())
        // ...
    }
}

NotesApp Composable代码

@Composable
fun NotesApp(noteDao: NoteDao) {
    val notes = remember { mutableStateListOf<Note>() }
    val title = remember { mutableStateOf("") }
    val content = remember { mutableStateOf("") }
    val coroutineScope = rememberCoroutineScope()

    LaunchedEffect(Unit) {
        coroutineScope.launch {
            notes.clear()
            notes.addAll(noteDao.getAllNotes())
        }
    }
    Column(modifier = Modifier.fillMaxSize()) {
        TextField(
            value = title.value,
            onValueChange = { title.value = it },
            label = { Text("Title") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )

        TextField(
            value = content.value,
            onValueChange = { content.value = it },
            label = { Text("Content") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )

        Button(
            onClick = {
                val newNote = Note(0, title.value, content.value)
                coroutineScope.launch {
                    noteDao.insert(newNote)
                    notes.clear()
                    notes.addAll(noteDao.getAllNotes())
                }
            },
            modifier = Modifier.padding(16.dp)
        ) {
            Text("Add Note")
        }

        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(notes.size) { index ->
                NoteItem(notes[index])
            }
        }
    }
}

@Composable
fun NoteItem(note: Note) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = note.title, style = MaterialTheme.typography.bodyMedium)
            Text(text = note.content, style = MaterialTheme.typography.bodySmall)
        }
    }
}

崩溃原因及修复方案

1. Room操作未在指定线程执行

Room强制要求所有数据库操作必须在非主线程执行,你的DAO方法是同步阻塞式的,直接在协程中调用时,默认调度器可能仍在主线程,触发崩溃。

修复:将DAO方法改为挂起函数,Room会自动处理线程调度:

@Dao
interface NoteDao {
    @Query("SELECT * FROM notes")
    suspend fun getAllNotes(): List<Note>

    @Insert
    suspend fun insert(note: Note): Long
}

2. 数据库实例冗余初始化

MainActivity中声明了全局db变量,但又在onCreate内创建了同名局部变量,虽然不直接导致崩溃,但属于冗余代码,统一为:

class MainActivity : ComponentActivity() {
    private lateinit var db: AppDatabase
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "note-database"
        ).build()
        setContent {
            // 此处添加你的主题配置
            NotesApp(db.noteDao())
        }
    }
}

3. LaunchedEffect无需嵌套协程

LaunchedEffect本身运行在协程上下文内,不需要额外嵌套coroutineScope.launch,直接调用挂起函数即可:

LaunchedEffect(Unit) {
    notes.clear()
    notes.addAll(noteDao.getAllNotes())
}

4. 可选优化:自动监听数据库变化

如果希望列表自动刷新,无需手动重复查询,可将DAO的查询方法改为返回Flow:

@Dao
interface NoteDao {
    @Query("SELECT * FROM notes")
    fun getAllNotes(): Flow<List<Note>>

    @Insert
    suspend fun insert(note: Note): Long
}

在Composable中用collectAsState自动收集Flow更新:

@Composable
fun NotesApp(noteDao: NoteDao) {
    val notes by noteDao.getAllNotes().collectAsState(initial = emptyList())
    val title = remember { mutableStateOf("") }
    val content = remember { mutableStateOf("") }
    val coroutineScope = rememberCoroutineScope()

    Column(modifier = Modifier.fillMaxSize()) {
        // TextField部分保持不变
        Button(
            onClick = {
                val newNote = Note(0, title.value, content.value)
                coroutineScope.launch {
                    noteDao.insert(newNote)
                    title.value = ""
                    content.value = "" // 可选:清空输入框
                }
            },
            modifier = Modifier.padding(16.dp)
        ) {
            Text("Add Note")
        }

        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(notes) { note -> // 直接遍历列表更简洁
                NoteItem(note)
            }
        }
    }
}

5. 实体类主键默认值优化

给自动生成的主键添加默认值,避免创建实例时必须传0:

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0, // 添加默认值
    val title: String,
    val content: String
)

内容的提问来源于stack exchange,提问作者gnoejh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 06:10:44