我正在参加 JetPack Compose 课程,并尝试运行一个示例项目,展示 ViewModel 与 Hilt 的结合使用。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Surface(color = MaterialTheme.colorScheme.background) {
val noteViewModel: NoteViewModel = hiltViewModel<NoteViewModel>()
// NoteApp(noteViewModel)
}
}
}
}
为了初始化 ViewModel 我也尝试过
noteViewModel by viewModels()
和
noteViewModel = hiltViewModel()
ViewModel代码如下:
@HiltViewModel
class NoteViewModel @Inject constructor(private val repository: NoteRepository) : ViewModel() {
//private var noteList = mutableStateListOf<Note>()
private val _noteList = MutableStateFlow<List<Note>>(emptyList())
val noteList = _noteList.asStateFlow()
init {
//noteList.addAll(NotesDataSource.loadDataNotes())
viewModelScope.launch(Dispatchers.IO) {
repository.getAllNotes().distinctUntilChanged().collect { listOfNotes ->
if (listOfNotes.isEmpty()) {
Log.d("JetNote", "EmptyList")
} else {
_noteList.value = listOfNotes
}
}
}
}
fun addNote(note: Note) = viewModelScope.launch {
repository.addNote(note)
}
fun updateNote(note: Note) = viewModelScope.launch {
repository.updateNote(note)
}
fun removeNote(note: Note) = viewModelScope.launch {
repository.deleteNote(note)
}
}
Hilt 的 AppModule 设置如下:
@InstallIn(SingletonComponent::class)
@Module
object AppModule {
@Singleton
@Provides
fun provideNotesDao(noteDatabase: NoteDatabase) : NoteDatabaseDao = noteDatabase.noteDao()
@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext context: Context) : NoteDatabase =
Room.databaseBuilder(
context,
NoteDatabase::class.java,
"notes_db"
).fallbackToDestructiveMigration().build()
}
注释存储库的代码:
class NoteRepository @Inject constructor(private val noteDatabaseDao: NoteDatabaseDao) {
suspend fun addNote(note: Note) = noteDatabaseDao.insert(note)
suspend fun updateNote(note: Note) = noteDatabaseDao.update(note)
suspend fun deleteNote(note: Note) = noteDatabaseDao.deleteNote(note)
suspend fun deleteAllNotes() = noteDatabaseDao.deleteAll()
fun getAllNotes(): Flow<List<Note>> = noteDatabaseDao.getNotes().flowOn(Dispatchers.IO).conflate()
}
运行该应用程序时我收到的错误是:
java.lang.RuntimeException: Cannot create an instance of class com.course.jetnote.screens.NoteViewModel
........................
Caused by: java.lang.NoSuchMethodException: com.course.jetnote.screens.NoteViewModel.<init> []
这是我的 Gradle 设置:
构建.gradle.kts:
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.course.jetnote"
compileSdk = 35
defaultConfig {
applicationId = "com.course.jetnote"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
packaging {
resources {
excludes += "/META-INF/gradle/incremental.annotation.processors"
excludes += "META-INF/androidx/room/room-compiler-processing/LICENSE.txt"
}
}
}
configurations.implementation {
exclude(group = "org.jetbrains", module = "annotations")
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.viewModelCompose)
implementation(libs.hilt.android)
implementation(libs.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.room.ktx)
implementation(libs.room.runtime)
implementation(libs.room.compiler)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.androidx.room.runtime.android)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
lib.versions.toml:
[versions]
agp = "8.8.2"
kotlin = "2.0.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.1"
composeBom = "2024.04.01"
androidxLifecycle = "2.8.7"
androidxHiltNavigationCompose = "1.0.0"
hilt = "2.56.2"
hiltExt = "1.0.0"
room = "2.6.0"
kotlinxCoroutines = "1.10.0"
kotlinxDatetime = "0.6.0"
kotlinxSerializationJson = "1.8.0"
roomRuntimeAndroid = "2.7.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltExt" }
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
#Database
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
#Kotlin Coroutines, serialization, datetime...
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
androidx-room-runtime-android = { group = "androidx.room", name = "room-runtime-android", version.ref = "roomRuntimeAndroid" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
您的 Gradle 文件中未正确设置 Hilt(和 Room)。要修复此问题,请按以下步骤操作:
首先,您需要Kotlin 符号处理 (KSP) 插件,以便 Hilt 能够在编译期间生成源代码。您需要在版本目录中添加以下插件:
KSP 版本由两部分组成。第一部分必须始终与您的 Kotlin 版本匹配。由于您使用的是 Kotlin 2.0.0,因此您必须使用此版本。第二部分是 KSP 版本本身。每当您更新 Kotlin 版本时,也必须更新 KSP。您可以在此处找到所有 KSP 版本的列表:https://github.com/google/ksp/releases
现在在你的 gradle 文件中使用它,就像使用其他插件一样:
分别
现在您有了 KSP,您需要切换依赖项
到
您还需要添加 Hilt Gradle 插件,就像上面添加 KSP 一样:
和
分别
这样就可以了,Hilt 现在就可以工作了。
但是,您的 Room 依赖项仍然存在问题,因此您的应用仍然无法运行。
首先,你混合使用了不同版本的 Room
room = "2.6.0"
和roomRuntimeAndroid = "2.7.1"
。这行不通,你总是需要使用相同的版本。所以,让我们用下面的代码替换它们:此外,您不需要明确使用该库
androidx-room-runtime-android
,因此您可以简单地将其从版本目录中删除,并将其使用implementation(libs.androidx.room.runtime.android)
在您的 gradle 文件中。现在,你遇到了与 Hilt 类似的问题:你需要替换
和
您已安装 KSP,所以以上就是所需的全部内容。您的应用现在应该可以运行了。
至于你在评论中提到的 gradle 文件中的
packaging
和configurations.implementation
代码块,我不明白这有什么必要。你最好先把这两个代码块都删掉,把这种优化留到以后再用(如果以后需要的话)。您可能还想看看另外两个兴趣点:
还有一个Room Gradle 插件可用。虽然不是必需的,但它可以帮助您将数据库从一个版本迁移到另一个版本。
正如评论中提到的,你的 Flow 处理方式很不合理。在视图模型中,你唯一需要做的就是:
这仅仅是将来自存储库的数据库流转换为 StateFlow。这就是它所需要的,您可以删除
init
块和 MutableStateFlow_noteList
。根据经验,永远不要在视图模型中收集流。如果您需要处理流的内容(您在这里没有这样做,但将来可能会需要),您可以使用各种可用于流的转换函数,例如
map
、等等flatMapLatest
。combine
类似地,
.flowOn(Dispatchers.IO).conflate()
在你的代码库中,这是多余的。使用 Kotlin 协程时,实际执行关键工作的代码部分负责切换到合适的调度器。在本例中,这是在 Room 库内部完成的操作。因此,你之后无需指定调度器。而且由于 StateFlow 始终是合并的(在数据库流转换为 StateFlow 之前,你不会执行任何开销高昂的操作),你也无需在此处显式地执行此操作。