以下来自不同地方的 Android 文档:
这个 StackOverflow 答案: 如何在 android studio 中添加 Hilt 对“libs.versions.toml”文件的依赖
我尝试使用 androidx.lifecycle:lifecycle.viewmodel.compose 工作,但我仍然收到以下错误,即使几天后我也无法弄清楚:
FATAL EXCEPTION: main (Ask Gemini)
Process: com.app, PID: 5391
java.lang.RuntimeException: Cannot create an instance of class com.app.ui.login.LoginViewModel
at androidx.lifecycle.viewmodel.internal.JvmViewModelProviders.createViewModel(JvmViewModelProviders.kt:40)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.android.kt:193)
...
以下是我尝试制作一个非常简单的登录页面的工作:
libs.version.toml
[versions]
agp = "8.7.3"
kotlin = "2.0.0"
coreKtx = "1.10.1"
...
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
composeBom = "2024.04.01"
#navigation
navVersion = "2.8.5"
jsonSerializationVersion = "1.7.3"
#hilt
kspVersion = "2.0.0-1.0.24"
hiltVersion = "2.51.1"
lifecycleViewmodelComposeVersion = "2.8.7"
[libraries]
#default generated
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
...
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-material3 = { group = "androidx.compose.material3", name = "material3" }
# password visibility icon
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended"}
#----
#----
#navigation
androidx-navigation-compose = {group = "androidx.navigation", name = "navigation-compose", version.ref = "navVersion"}
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "jsonSerializationVersion"}
#----
#hilt
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltVersion"}
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hiltVersion"}
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelComposeVersion"}
[plugins]
#default generated
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" }
#----
#navigation
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
#hilt
kotlinAndroidKsp = { id = "com.google.devtools.ksp", version.ref = "kspVersion"}
hiltAndroid = { id = "com.google.dagger.hilt.android", version.ref = "hiltVersion"}
build.gradle.kts(应用程序)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
//generated
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
//----
//hilt
alias(libs.plugins.hiltAndroid) apply false
alias(libs.plugins.kotlinAndroidKsp) apply false
}
build.gradle.kts(:应用程序)
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
//navigation
alias(libs.plugins.kotlin.serialization)
//hilt
alias(libs.plugins.hiltAndroid)
alias(libs.plugins.kotlinAndroidKsp)
}
android {
namespace = "com.app"
compileSdk = 34
defaultConfig {
applicationId = "com.app"
minSdk = 33
targetSdk = 34
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
}
}
dependencies {
//default generated
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)
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)
// password visibility icon
implementation(libs.androidx.material.icons.extended)
//----
//----
//navigation
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlinx.serialization.json)
//----
//hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
implementation(libs.androidx.lifecycle.viewmodel.compose)
//----
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
...
tools:targetApi="31"
android:name=".App">
<activity
android:name=".MainActivity"
...
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
应用程序.kt
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App: Application()
MainActivity.kt
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.app.ui.theme.AppTheme
import com.app.ui.login.Login
import dagger.hilt.android.AndroidEntryPoint
private const val TAG = "MainActivity"
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Main(
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Main(modifier: Modifier = Modifier) {
val navController = rememberNavController()
NavHost(navController, startDestination = Login) {
composable<Login> {
Login(
modifier = modifier,
onRegisterClick = { Log.d(TAG, "Nav to register success")/*navController.navigate(route = Register)*/ },
onLoginSuccess = { Log.d(TAG, "Login Success") }
)
}
}
}
登录.kt
import androidx.compose.foundation.layout.Arrangement
...
import androidx.lifecycle.viewmodel.compose.viewModel
import com.app.ui.shared.PasswordField
import com.app.ui.shared.UsernameField
@Composable
fun Login(
modifier: Modifier = Modifier,
vm: LoginViewModel = viewModel(),
onRegisterClick: () -> Unit = {},
onLoginSuccess: () -> Unit = {}
) {
//Also tried: val vm:LoginViewModel by viewModel()
val uiState by vm.uiState.collectAsStateWithLifecycle()
val kbController = LocalSoftwareKeyboardController.current
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val textFieldModifier = Modifier.fillMaxWidth().padding(16.dp,4.dp)
UsernameField(uiState.username, vm::onUsernameChange, textFieldModifier)
PasswordField(uiState.password, vm::onPasswordChange, textFieldModifier)
Button(
onClick = {
kbController?.hide()
vm.onLoginClick(onLoginSuccess = onLoginSuccess)
},
modifier = Modifier.fillMaxWidth().padding(16.dp,8.dp)
) {
Text(text = "Sign In", fontSize = 16.sp)
}
...
}
}
登录ViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.app.domain.BasicAuthUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
...
@HiltViewModel
class LoginViewModel @Inject constructor(
private val basicAuthUseCase: BasicAuthUseCase
): ViewModel() {
private val _uiState = MutableStateFlow(LoginState())
val uiState: StateFlow<LoginState> = _uiState.asStateFlow()
fun onUsernameChange(username: String) =
_uiState.update { it.copy(username = username) }
fun onPasswordChange(password: String) =
_uiState.update { it.copy(password = password) }
fun onLoginClick(onLoginSuccess: () -> Unit) {
...
}
fun hideAfterDelay(duration: Long){
...
}
}
HiltBindings.kt
import com.app.domain.BasicAuthUseCase
import com.app.domain.BasicAuthUseCaseImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
abstract class HiltBindings {
@Binds
abstract fun bindBasicAuthUseCase(impl: BasicAuthUseCaseImpl): BasicAuthUseCase
}
BasicAuthUseCase.kt
interface BasicAuthUseCase {
suspend fun verifyUser(username: String, password: String): Boolean
suspend fun isUsernameExist(username: String): Boolean
suspend fun registerUser(username: String,password: String): Boolean
}
回顾几年前我设法使其工作的地方,我进行了以下更改,并通过将 androidx.lifecycle:lifecycle.viewmodel.compose 替换为 androidx.hilt:hilt.navigation.compose 来设法使其工作:
libs.version.toml
[versions]
...
#hilt
kspVersion = "2.0.0-1.0.24"
hiltVersion = "2.51.1"
#lifecycleViewmodelComposeVersion = "2.8.7"
hiltNavigationComposeVersion = "1.2.0"
[libraries]
...
#hilt
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltVersion"}
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hiltVersion"}
#androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelComposeVersion"}
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationComposeVersion"}
[plugins]
...
build.gradle.kts(:应用程序)
...
dependencies {
...
//hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
// implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.hilt.navigation.compose)
//----
}
登录.kt
import androidx.compose.foundation.layout.Arrangement
...
import androidx.hilt.navigation.compose.hiltViewModel
import com.app.ui.shared.PasswordField
import com.app.ui.shared.UsernameField
@Composable
fun Login(
modifier: Modifier = Modifier,
vm: LoginViewModel = hiltViewModel(),
onRegisterClick: () -> Unit = {},
onLoginSuccess: () -> Unit = {}
) {
...
}
我可以帮助了解我在 androidx.lifecycle:lifecycle.viewmodel.compose 方面做错了什么吗?据说这是推荐的解决方案(Compose 和其他库),但我无法使其工作。
使用 Hilt 时需要调用
hiltViewModel()
来获取视图模型,而不是 viewModel()
。
该函数位于此依赖项中,您需要将其添加到构建设置中:
androidx.hilt:hilt-navigation-compose:1.2.0
也许你对此感到困惑
androidx.lifecycle:lifecycle-viewmodel-compose
您已将其与其他 Hilt 依赖项组合在一起。然而,这个与希尔特无关。