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

屏幕旋转时保留ViewModel:Dagger注入致登录页状态丢失

解决屏幕旋转时ViewModel状态丢失与API调用重复触发问题

你的问题核心在于直接通过Dagger注入ViewModel实例,没有利用ViewModel自身的生命周期管理机制——屏幕旋转时Activity重建,Dagger会提供全新的ViewModel实例,导致之前的状态和API调用状态全部丢失。下面是具体的解决方案,一步步帮你修复这个问题:

1. 先理清问题根源

ViewModel的设计初衷就是在配置变化(比如屏幕旋转)时保留状态,它的生命周期独立于Activity的重建。但如果直接用@Inject在Activity里注入ViewModel,Dagger会在每次Activity创建时都生成新实例,完全绕过了ViewModel的生命周期管理机制。

另外,你在onDestroy里取消API调用也不合理——屏幕旋转触发的onDestroy只是临时销毁Activity,ViewModel此时并不会被销毁,真正需要取消API调用的时机是ViewModel被彻底销毁(比如用户退出Activity)。

2. 调整ViewModel的实现与注入方式

我们需要让Dagger提供ViewModel.Factory,然后通过ViewModelProvider获取ViewModel,这样系统就能在配置变化时保留原ViewModel实例。

步骤1:改造LoginViewModel

用viewModelScope管理API调用的协程,它会在ViewModel真正销毁时自动取消所有协程,无需手动在Activity里处理:

class LoginViewModel @Inject constructor(
    private val loginRepository: LoginRepository
) : ViewModel() {
    // 用LiveData暴露登录状态
    private val _loginState = MutableLiveData<LoginState>()
    val loginState: LiveData<LoginState> = _loginState

    fun triggerLogin(username: String, password: String) {
        // 避免重复触发加载
        if (_loginState.value is LoginState.Loading) return
        
        viewModelScope.launch {
            _loginState.value = LoginState.Loading
            try {
                val loginResult = loginRepository.login(username, password)
                _loginState.value = LoginState.Success(loginResult)
            } catch (e: Exception) {
                _loginState.value = LoginState.Error(e.message ?: "登录失败")
            }
        }
    }

    // 密封类定义登录状态
    sealed class LoginState {
        object Loading : LoginState()
        data class Success(val data: LoginResponse) : LoginState()
        data class Error(val message: String) : LoginState()
    }
}

步骤2:创建ViewModelFactory

让Dagger注入这个Factory,它负责提供ViewModel实例:

class LoginViewModelFactory @Inject constructor(
    private val loginViewModel: Provider<LoginViewModel>
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        require(modelClass.isAssignableFrom(LoginViewModel::class.java)) {
            "Unknown ViewModel class: ${modelClass.name}"
        }
        return loginViewModel.get() as T
    }
}

步骤3:修改LoginActivity的ViewModel获取逻辑

不再直接注入ViewModel,而是通过ViewModelProvider获取,确保配置变化时复用原实例:

class LoginActivity : AppCompatActivity() {
    @Inject lateinit var loginViewModelFactory: LoginViewModelFactory
    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        // 注入ViewModelFactory
        (application as YourApp).appComponent.inject(this)

        // 获取ViewModel——屏幕旋转时会复用原实例
        loginViewModel = ViewModelProvider(this, loginViewModelFactory)[LoginViewModel::class.java]

        // 观察登录状态变化
        loginViewModel.loginState.observe(this) { state ->
            when (state) {
                LoginViewModel.LoginState.Loading -> showLoading()
                is LoginViewModel.LoginState.Success -> handleLoginSuccess(state.data)
                is LoginViewModel.LoginState.Error -> showError(state.message)
            }
        }

        // 按钮点击触发登录
        loginBtn.setOnClickListener {
            val username = usernameEt.text.toString().trim()
            val password = passwordEt.text.toString().trim()
            loginViewModel.triggerLogin(username, password)
        }
    }

    // 移除onDestroy里的API取消逻辑——viewModelScope会自动处理
    // override fun onDestroy() {
    //     super.onDestroy()
    //     // 这里的取消逻辑可以删掉
    // }
}

步骤4:配置Dagger组件

确保你的AppComponent能正确注入LoginViewModelFactory:

@Singleton
@Component(modules = [/* 你的Repository、网络模块等 */])
interface AppComponent {
    fun inject(activity: LoginActivity)
    fun loginViewModelFactory(): LoginViewModelFactory
}

3. 为什么这样能解决问题?

  • ViewModelProvider的作用:它会跟踪ViewModel的生命周期,配置变化时不会销毁原ViewModel,新的Activity会直接复用已有实例,状态(比如LiveData的加载状态、协程执行情况)完全保留。
  • viewModelScope的自动管理:ViewModel的onCleared()方法会在Activity真正被销毁(比如用户点击返回键退出)时调用,此时viewModelScope会自动取消所有未完成的协程,无需手动在Activity的onDestroy里处理。
  • Dagger与ViewModel的配合:通过Factory注入,既保留了Dagger的依赖注入能力,又让ViewModel的生命周期被系统正确管理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:47:34