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

如何实现带Mutable State的登录ViewModel?(附代码求指导)

完善带Mutable State的LoginViewModel指导

你刚入行1个月,已经完成了LoginRepository接口、实现类,接下来我会一步步帮你完善LoginViewModel的Mutable State逻辑:

你的现有代码

LoginRepository接口

interface LoginRepository {
    suspend fun postLogin(
        loginRequest: LoginRequest
    ): Resource<Login>
}

LoginRepositoryImpl实现类

@Singleton
class LoginRepositoryImpl @Inject constructor(
    private val api: ApiClient,
): LoginRepository {
    override suspend fun postLogin(loginRequest: LoginRequest): Resource<Login> {
        return try {
            val result = api.login(loginRequest)
            Resource.Success(result.toLoginDomain())
        } catch (e: IOException) {
            e.printStackTrace()
            Resource.Error("Could not load login")
        } catch (e: HttpException) {
            e.printStackTrace()
            Resource.Error("Internet connection problem")
        }
    }
}

未完成的LoginViewModel

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val useCase: LoginUseCase
) : ViewModel() {

    private val _state = mutableStateOf<LoginState>(LoginState.InProgress)
    val state: State<LoginState> get() = _state

    fun login(username: String, password: String) = viewModelScope.launch {
        val login = async { useCase.execute(loginRequest = LoginRequest(username,password)) }
        _state = _state.value
    }
}

完善步骤

1. 定义LoginState密封类

首先需要用密封类明确登录的所有状态,让ViewModel能精准通知UI状态变化:

sealed class LoginState {
    object Idle : LoginState()       // 初始状态,未触发登录
    object Loading : LoginState()    // 登录请求中
    data class Success(val data: Login) : LoginState() // 登录成功,携带用户数据
    data class Error(val msg: String) : LoginState()    // 登录失败,携带错误信息
}

如果习惯原代码的InProgress,可以把Loading改成InProgress,保持命名一致即可

2. 重构LoginViewModel逻辑

原代码没有处理请求结果,也未正确更新状态,修改后如下:

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val useCase: LoginUseCase
) : ViewModel() {

    // 初始状态设为Idle更贴合实际场景
    private val _state = mutableStateOf<LoginState>(LoginState.Idle)
    val state: State<LoginState> get() = _state

    fun login(username: String, password: String) = viewModelScope.launch {
        // 发起请求前,切换到加载状态
        _state.value = LoginState.Loading
        
        try {
            // 直接执行登录用例,无需额外async(除非要并行任务)
            val loginResult = useCase.execute(LoginRequest(username, password))
            
            // 根据返回的Resource更新状态
            when (loginResult) {
                is Resource.Success -> {
                    _state.value = LoginState.Success(loginResult.data)
                }
                is Resource.Error -> {
                    _state.value = LoginState.Error(loginResult.message ?: "登录失败,请重试")
                }
            }
        } catch (e: Exception) {
            // 捕获意外异常,避免状态停留在加载中
            _state.value = LoginState.Error(e.message ?: "发生未知错误")
        }
    }
}

3. 核心注意事项

  • 状态流转要完整:从Idle→Loading→Success/Error,确保每个阶段UI都能收到通知
  • 避免冗余协程操作:viewModelScope.launch已经是协程上下文,直接调用挂起函数execute即可,不需要async
  • 异常兜底处理:除了Repository返回的错误,还要捕获全局异常,防止加载状态卡死
  • 状态数据封装:用密封类的data class携带必要数据(比如成功时的用户信息),UI层直接取用即可

4. 补充LoginUseCase示例(如果未实现)

如果你的UseCase只是封装Repository调用,参考实现:

class LoginUseCase @Inject constructor(
    private val loginRepo: LoginRepository
) {
    suspend fun execute(request: LoginRequest): Resource<Login> {
        return loginRepo.postLogin(request)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:40:22