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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:16:47