如何实现带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
相关产品推荐
相关产品推荐

