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

Jetpack Compose中ViewModel结合Room数据库导致应用启动崩溃问题求助

Jetpack Compose中ViewModel结合Room数据库导致应用启动崩溃问题求助

我现在遇到一个头疼的问题——当我打开调用ViewModel的Composable视图时,应用直接崩溃了。我怀疑是ViewModel里的Room数据库配置出了问题,但之前听说直接给ViewModel传Context不是最佳实践,所以现在有点迷茫。下面是我的代码和错误日志,麻烦大家帮我看看哪里出问题了!

我的现有代码

ViewModel 代码

class DataModel(): ViewModel() {
    lateinit var database: AppDatabase

    fun setDatabase(context: Context) {
        database = Room.databaseBuilder(
            context,
            AppDatabase::class.java, "database"
        ).build()
    }

    val data: StateFlow<List<Data>> = flow {
        emit(database.dataDao().getAll())
    } as StateFlow<List<Data>>

    fun addData(data: Data) {
        viewModelScope.launch {
            database.dataDao().insert(data)
        }
    }
}

Room Database 代码

@Database(entities = [Data::class], version = 1)
@TypeConverters(BooleanArrayConverter::class)
abstract class AppDatabase: RoomDatabase() {
    abstract fun dataDao(): DataDao // 原代码写的是data(),已修正为对应DAO接口的命名
}

DAO 接口代码

@Dao
interface DataDao {
    @Query("SELECT * FROM data")
    suspend fun getAll(): List<Data>

    @Insert
    suspend fun insert(vararg data: Data)
}

Composable 视图代码

fun View(
    dataModel: DataModel = viewModel(),
) {
    val context = LocalContext.current
    Button(
        onClick = { dataModel.setDatabase(context) },
    ) {
        Text("Button")
    }
}

崩溃错误日志

FATAL EXCEPTION: main
Process: com.example.app, PID: 12733
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Constructor.newInstance0(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
    at androidx.lifecycle.viewmodel.internal.JvmViewModelProviders.createViewModel(JvmViewModelProviders.kt:38)
    at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.android.kt:185)
    at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.android.kt:309)
    at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.android.kt:269)
    at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.android.kt:142)
    at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.android.kt:112)
    at androidx.lifecycle.viewmodel.ViewModelProviderImpl_androidKt.createViewModel(ViewModelProviderImpl.android.kt:34)
    at androidx.lifecycle.viewmodel.ViewModelProviderImpl.getViewModel$lifecycle_viewmodel_release(ViewModelProviderImpl.kt:60)
    at androidx.lifecycle.viewmodel.ViewModelProviderImpl.getViewModel$lifecycle_viewmodel_release$default(ViewModelProviderImpl.kt:43)
    at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.android.kt:92)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt__ViewModelKt.get(ViewModel.kt:172)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(Unknown Source:1)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt__ViewModelKt.viewModel(ViewModel.kt:106)
    at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(Unknown Source:1)
    at com.example.app.View.NewJournalEntrySheet(View.kt:174)
    at com.example.app.ComposableSingletons$ViewKt$lambda-1$1.invoke(JournalEntryScreen.kt:45)
    at com.example.app.ComposableSingletons$ViewKt$lambda-1$1.invoke(JournalEntryScreen.kt:44)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:130)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:51)
    at androidx.compose.material3.ModalBottomSheetKt$ModalBottomSheetContent$7.invoke(ModalBottomSheet.kt:341)
    at androidx.compose.material3.ModalBottomSheetKt$ModalBottomSheetContent$7.invoke(ModalBottomSheet.kt:289)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:121)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:51)
    at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:126)
    at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:121)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:51)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:364)
    at androidx.compose.material3.SurfaceKt.Surface-T9BRK9s(Surface.kt:105)
    at androidx.compose.material3.ModalBottomSheetKt.ModalBottomSheetContent-IQkwcL4(ModalBottomSheet.kt:218)
    at androidx.compose.material3.ModalBottomSheetKt$ModalBottomSheet$3.invoke(ModalBottomSheet.kt:175)
    at androidx.compose.material3.Modal...

我自己的猜测

我本来想在ViewModel里创建数据库实例,但又知道直接传Context给ViewModel不是好做法,所以才搞了个按钮点击来初始化数据库,但没想到还没点按钮应用就崩了...


问题根源分析

后来我才反应过来,崩溃的核心原因其实很直白:当ViewModel被创建的时候,data这个StateFlow就会立即初始化,而在初始化Flow的Lambda里,我直接调用了database.dataDao().getAll(),但此时database还是lateinit修饰的空值(要等点击按钮才会初始化),这就直接触发了未初始化属性访问的异常,最终导致应用崩溃。

另外还有几个隐藏的小问题:

  1. 直接给ViewModel传Context确实存在内存泄漏风险,不是最佳实践;
  2. 把普通Flow强转成StateFlow的写法不规范,应该用官方推荐的stateIn操作符;
  3. 原AppDatabase的抽象方法命名和DAO接口不匹配,会导致编译错误。

解决方案

我整理了两个可行的方案,大家可以根据项目规模选择:

方案一:Application层创建数据库单例(简单易上手,适合小型项目)

这种方式不需要额外依赖,直接在Application类里初始化数据库单例,ViewModel直接复用这个单例:

  1. 自定义Application类:
class MyApp: Application() {
    companion object {
        lateinit var database: AppDatabase
    }

    override fun onCreate() {
        super.onCreate()
        // 在Application启动时初始化数据库
        database = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database"
        ).build()
    }
}
  1. 在AndroidManifest.xml里注册这个Application:
<application
    android:name=".MyApp"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    ...>
    ...
</application>
  1. 修改ViewModel:
class DataModel(): ViewModel() {
    private val database = MyApp.database

    // 用stateIn正确创建StateFlow
    val data: StateFlow<List<Data>> = flow {
        emit(database.dataDao().getAll())
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000), // 订阅者消失5秒后停止数据流
        initialValue = emptyList() // 初始值设为空列表,避免UI空指针
    )

    fun addData(data: Data) {
        viewModelScope.launch {
            database.dataDao().insert(data)
        }
    }
}
  1. 简化Composable视图:
fun View(
    dataModel: DataModel = viewModel(),
) {
    // 观察数据状态更新UI
    val dataList by dataModel.data.collectAsState()
    
    // 示例:展示数据列表
    LazyColumn {
        items(dataList) { item ->
            Text(text = item.toString())
        }
    }
}

方案二:使用Hilt依赖注入(推荐的最佳实践,适合中大型项目)

用Hilt管理依赖可以彻底解决ViewModel依赖Context的问题,同时符合Jetpack架构规范:

  1. 按照官方文档添加Hilt的依赖和基础配置;
  2. 创建Database模块提供依赖:
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    @Provides
    fun provideDataDao(database: AppDatabase): DataDao {
        return database.dataDao()
    }

    @Provides
    @Singleton
    fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java, "database"
        ).build()
    }
}
  1. 修改ViewModel,用Hilt注入DAO:
@HiltViewModel
class DataModel @Inject constructor(
    private val dataDao: DataDao // 直接注入DAO,无需关心数据库实例
): ViewModel() {
    val data: StateFlow<List<Data>> = flow {
        emit(dataDao.getAll())
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )

    fun addData(data: Data) {
        viewModelScope.launch {
            dataDao.insert(data)
        }
    }
}
  1. 简化Composable视图:
@Composable
fun View(
    dataModel: DataModel = hiltViewModel(),
) {
    val dataList by dataModel.data.collectAsState()
    
    // 你的UI逻辑...
}

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 11:04:33