androidx.lifecycle:lifecycle.viewmodel.compose 与 androidx.hilt:hilt.navigation.compose

问题描述 投票:0回答:1

以下来自不同地方的 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 和其他库),但我无法使其工作。

android android-jetpack-compose android-viewmodel dagger-hilt
1个回答
0
投票

使用 Hilt 时需要调用

hiltViewModel()
来获取视图模型,而不是
viewModel()

该函数位于此依赖项中,您需要将其添加到构建设置中:

androidx.hilt:hilt-navigation-compose:1.2.0

也许你对此感到困惑

androidx.lifecycle:lifecycle-viewmodel-compose

您已将其与其他 Hilt 依赖项组合在一起。然而,这个与希尔特无关。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.