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

多模块Jetpack Compose应用中如何正确处理HTTP错误码并避免模块依赖耦合

多模块Jetpack Compose应用中如何正确处理HTTP错误码并避免模块依赖耦合

我太懂你现在的困扰了——本来想靠多模块架构把各个层的职责拆得明明白白,结果数据层直接用上了Retrofit的HttpException,等于把网络层的实现细节直接泄露到了数据层,完全没享受到模块解耦的好处,还要处理不同HTTP错误码给前端返回对应提示对吧?咱们一步步来解决这个问题,既解耦模块,又能精准处理错误码。

核心问题拆解

你现在的问题本质是模块边界被打破了:数据层不应该依赖网络层的具体异常类(HttpException),网络层的细节(比如Retrofit的实现、HTTP状态码)应该被网络层自己封装好,只给上层暴露干净的业务接口。同时,通用的错误类型、结果类型应该抽离到公共模块,避免各个模块互相依赖。

具体解决方案步骤

1. 抽离通用类型到基础模块

首先,把Result密封接口、DataError密封接口和它的Network枚举,放到一个独立的base(或者core)模块里——这个模块是所有业务模块的依赖,只放通用的、无业务逻辑的类型。这样网络层、数据层、UI层都能使用这些通用类型,不用互相依赖。

示例基础模块代码:

// base模块 - Result.kt
sealed interface Result<out D, out E : Error> {
    data class Success<out D, out E : Error>(val data: D) : Result<D, E>
    data class Error<out D, out E : Error>(val error: E) : Result<D, E>
}

// base模块 - DataError.kt
sealed interface DataError : Error {
    enum class Network : DataError {
        BAD_REQUEST,
        UNAUTHORIZED,
        CONFLICT,
        INTERNAL_SERVER_ERROR,
        NO_CONNECTION,
        UNKNOWN
    }
}

2. 改造网络层,统一处理HTTP异常

网络层的核心职责是处理所有和网络相关的细节:发起请求、捕获网络异常、转换HTTP状态码为通用错误类型。我们可以给AuthApi做一个包装类(比如AuthNetworkSource),把所有网络请求的异常处理都放在这里,不让HttpException暴露到数据层。

首先在网络层新增这个包装类:

// 网络层 - AuthNetworkSource.kt
class AuthNetworkSource @Inject constructor(
    private val authApi: AuthApi
) {
    // 通用网络请求异常处理工具
    private suspend fun <T> safeApiCall(call: suspend () -> T): Result<T, DataError.Network> {
        return try {
            val data = call()
            Result.Success(data)
        } catch (e: HttpException) {
            val error = when (e.code()) {
                400 -> DataError.Network.BAD_REQUEST
                401 -> DataError.Network.UNAUTHORIZED
                409 -> DataError.Network.CONFLICT
                500 -> DataError.Network.INTERNAL_SERVER_ERROR
                else -> DataError.Network.UNKNOWN
            }
            Result.Error(error)
        } catch (e: IOException) {
            // 处理无网络、连接超时等情况
            Result.Error(DataError.Network.NO_CONNECTION)
        } catch (e: Exception) {
            Result.Error(DataError.Network.UNKNOWN)
        }
    }

    // 登录请求:封装Retrofit调用,返回处理好的Result
    suspend fun login(request: LoginRequest): Result<TokenPair, DataError.Network> {
        return safeApiCall { authApi.login(request) }
    }

    // 同理处理注册请求(记得给AuthApi的register函数补返回值,比如Response<Void>或用户信息)
    suspend fun register(request: RegisterRequest): Result<Unit, DataError.Network> {
        return safeApiCall { authApi.register(request) }
    }
}

然后在NetworkModule里让Dagger自动处理AuthNetworkSource的依赖(它已经用@Inject标注了构造函数),这样数据层可以依赖AuthNetworkSource,而不是直接依赖AuthApi。

3. 改造数据层,移除Retrofit依赖

现在数据层的AuthRepositoryImpl只需要依赖AuthNetworkSource,不用再处理HttpException,专注于业务逻辑(比如缓存Token、转换数据格式):

// 数据层 - AuthRepositoryImpl.kt
class AuthRepositoryImpl @Inject constructor(
    private val authNetworkSource: AuthNetworkSource,
    private val tokenManager: TokenManager // 比如用DataStore缓存Token
) : AuthRepository {
    override suspend fun signIn(email: String, password: String): Result<TokenPair, DataError.Network> {
        val result = authNetworkSource.login(LoginRequest(email, password))
        // 业务逻辑:如果登录成功,缓存Token
        if (result is Result.Success) {
            tokenManager.saveAccessToken(result.data.accessToken)
            tokenManager.saveRefreshToken(result.data.refreshToken)
        }
        return result
    }

    override suspend fun signUp(email: String, password: String): Result<Unit, DataError.Network> {
        return authNetworkSource.register(RegisterRequest(email, password))
    }
}

这样改造后,数据层完全和Retrofit的细节隔离了——它既不知道HttpException,也不知道Retrofit的存在,只关心拿到的Result类型,完美符合多模块解耦的要求。

4. UI层根据错误类型显示对应提示

最后到UI层,你可以直接根据Result.Error里的DataError.Network枚举值,显示不同的用户提示:

// UI层 - 示例:登录ViewModel或Composable函数
when (val result = viewModel.signInState.value) {
    is Result.Success -> {
        // 登录成功,导航到主页
        navController.navigate(Screen.Home.route)
    }
    is Result.Error -> {
        val message = when (result.error) {
            DataError.Network.BAD_REQUEST -> "请求参数错误,请检查输入格式"
            DataError.Network.UNAUTHORIZED -> "邮箱或密码错误,请重新输入"
            DataError.Network.CONFLICT -> "该邮箱已被注册,请直接登录"
            DataError.Network.INTERNAL_SERVER_ERROR -> "服务器内部错误,请稍后再试"
            DataError.Network.NO_CONNECTION -> "网络连接失败,请检查网络设置"
            DataError.Network.UNKNOWN -> "未知错误,请稍后再试"
        }
        // 显示Snackbar或Toast
        SnackbarHostState.showSnackbar(message)
    }
}

为什么这样做是对的?

  1. 单一职责原则:网络层管网络请求和异常,数据层管业务逻辑,UI层管用户展示,每个模块只做自己的事。
  2. 模块解耦:数据层不再依赖网络层的具体实现,哪怕以后把Retrofit换成OkHttp或者其他网络库,数据层和UI层都不用改代码。
  3. 可维护性:所有网络异常的处理逻辑都集中在网络层的safeApiCall函数里,不用每个请求都重复写try-catch,改错误码映射只需要改一个地方。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 11:03:10