如何用Jetpack Compose+Kotlin Flow+Retrofit处理无网络及重试逻辑
解决方案
要实现无网络/请求错误时展示指定UI,并让重试按钮重新发起请求,需从状态管理、UI回调、请求触发逻辑三个层面调整现有代码,具体步骤如下:
1. 补充ViewModel层管理UI状态
ViewModel负责连接业务逻辑与UI,收集请求流状态并对外暴露可观察状态,同时提供触发请求的方法:
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class LoginViewModel @Inject constructor( private val authUseCase: AuthUseCase ) : ViewModel() { private val _loginState = MutableStateFlow<Resource<Login>>(Resource.Loading()) val loginState: StateFlow<Resource<Login>> = _loginState.asStateFlow() // 触发登录请求的方法,供UI调用 fun performLogin(loginRequest: LoginRequest) { viewModelScope.launch { authUseCase.execute(loginRequest).collect { resource -> _loginState.value = resource } } } }
2. 修改错误UI组件,添加重试回调
将NetworkErrorDialog的硬编码导航逻辑改为回调函数,让父组件决定重试行为,提升组件复用性:
@Composable fun NetworkErrorDialog( onRetry: () -> Unit // 重试回调,由父组件实现具体逻辑 ) { Box( modifier = Modifier .fillMaxSize() .padding(horizontal = 8.dp) ) { Column( modifier = Modifier.align(TopStart), horizontalAlignment = Alignment.CenterHorizontally, ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { Image( painter = painterResource(id = R.drawable.img_epod), contentDescription = "logo", modifier = Modifier.size(width = 80.dp, height = 50.dp) ) } } Column( modifier = Modifier .fillMaxWidth() .align(Center) .offset(y = -50.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Image( painter = painterResource(id = R.drawable.img_no_internet_dialog), contentDescription = "无网络图标", alignment = Center, modifier = Modifier.size(300.dp) ) Text( text = stringResource(id = R.string.NoInternetConnection), style = TextStyle( fontFamily = Poppins, fontSize = 28.sp, fontWeight = FontWeight.Bold ) ) Text( text = stringResource(id = R.string.NoInternetConnectionSub), style = TextStyle( fontFamily = Poppins, fontSize = 13.sp, fontWeight = FontWeight.Thin, color = Color.Gray, textAlign = TextAlign.Center ) ) Spacer(modifier = Modifier.height(50.dp)) Button( onClick = onRetry, modifier = Modifier.width(147.dp), colors = ButtonDefaults.buttonColors(Blue500) ) { Text( text = stringResource(id = R.string.TryAgain), style = TextStyle( color = Color.White, fontWeight = FontWeight.Bold ) ) } } } }
3. 在登录页面根据状态切换UI
在登录主Composable中,收集ViewModel的状态,根据状态展示加载、成功、错误UI,并实现重试逻辑:
@Composable fun LoginScreen( viewModel: LoginViewModel = hiltViewModel(), navController: NavController ) { // 保存用户输入的用户名和密码 val usernameState = remember { mutableStateOf("") } val passwordState = remember { mutableStateOf("") } // 收集登录状态 val loginState by viewModel.loginState.collectAsState() // 根据状态展示不同UI when (loginState) { is Resource.Loading -> { // 加载中:显示进度指示器 Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } } is Resource.Success -> { // 登录成功:导航到主页 LaunchedEffect(Unit) { navController.navigate("home") { popUpTo("login") { inclusive = true } } } } is Resource.Error -> { // 错误状态:显示无网络/错误UI,点击重试重新发起请求 NetworkErrorDialog(onRetry = { val loginRequest = LoginRequest( username = usernameState.value, password = passwordState.value ) viewModel.performLogin(loginRequest) }) } } // 正常登录UI(仅在非加载/成功/错误状态下显示) if (loginState !is Resource.Loading && loginState !is Resource.Success && loginState !is Resource.Error) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { TextField( value = usernameState.value, onValueChange = { usernameState.value = it }, label = { Text("用户名") } ) TextField( value = passwordState.value, onValueChange = { passwordState.value = it }, label = { Text("密码") }, visualTransformation = PasswordVisualTransformation() ) Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { val loginRequest = LoginRequest( username = usernameState.value, password = passwordState.value ) viewModel.performLogin(loginRequest) } ) { Text("登录") } } } }
关键说明
- 状态流转:Repository通过Flow返回请求状态,ViewModel收集并转换为StateFlow供UI观察,实现响应式UI更新。
- 重试逻辑:点击重试按钮时,复用用户已输入的账号密码重新发起请求,避免刷新页面丢失用户输入。
- 错误区分:Repository中
IOException对应无网络场景,HttpException对应其他HTTP错误,都会触发错误UI展示,符合需求。
内容的提问来源于stack exchange,提问作者Yuba
相关产品推荐
相关产品推荐

