渐进式编程课
KMP 目录
第 11 课2026.07.27

网络请求不仅会成功,也可能出现断网、HTTP 状态异常或数据解析失败。共享层可以用密封类型把结果完整交给调用方,让 Android 和 iOS 对所有情况进行明确处理,而不是用空对象或 UI 占位状态掩盖错误。

第 11 课:用密封类型表达数据结果

  • 日期:2026-07-27
  • 课程序号:11
  • 知识点:使用 sealed interface 明确表达成功与失败

用途

网络请求不仅会成功,也可能出现断网、HTTP 状态异常或数据解析失败。共享层可以用密封类型把结果完整交给调用方,让 Android 和 iOS 对所有情况进行明确处理,而不是用空对象或 UI 占位状态掩盖错误。

核心概念

  • 密封类型限制了所有合法分支,when 可以穷尽处理。
  • 成功分支携带真实业务数据。
  • 失败分支携带数据层识别出的错误类型。
  • 错误应在发生它的数据层归类,UI 只决定如何展示。
  • 协程取消不是普通业务失败,不应转换成错误页面状态。

示例代码

定义共享结果与错误类型:

kotlin
sealed interface DataError {
    data object Network : DataError
    data class Http(val statusCode: Int) : DataError
    data object InvalidPayload : DataError
}

sealed interface DataResult<out T> {
    data class Success<T>(val value: T) : DataResult<T>
    data class Failure(val error: DataError) : DataResult<Nothing>
}

Repository 接收已经由网络层归类的结果,并映射成功数据:

kotlin
interface UserRemoteDataSource {
    suspend fun fetchUser(id: Long): DataResult<UserDto>
}

class UserRepository(
    private val remote: UserRemoteDataSource,
) {
    suspend fun loadUser(id: Long): DataResult<User> {
        return when (val result = remote.fetchUser(id)) {
            is DataResult.Success -> {
                val dto = result.value
                DataResult.Success(
                    User(
                        id = dto.id,
                        displayName = dto.name,
                    ),
                )
            }

            is DataResult.Failure -> result
        }
    }
}

调用方必须明确处理两个结果分支:

kotlin
when (val result = repository.loadUser(1L)) {
    is DataResult.Success -> println(result.value.displayName)
    is DataResult.Failure -> println("加载失败:${result.error}")
}

网络异常、HTTP 状态和解析异常应由 UserRemoteDataSource 根据实际响应归类,Repository 不猜测错误来源。

5 分钟练习

给 DataError 增加未登录错误 Unauthorized,并在调用方的失败分支中单独识别它。

参考答案

kotlin
sealed interface DataError {
    data object Network : DataError
    data class Http(val statusCode: Int) : DataError
    data object InvalidPayload : DataError
    data object Unauthorized : DataError
}

when (val result = repository.loadUser(1L)) {
    is DataResult.Success -> println(result.value.displayName)
    is DataResult.Failure -> when (result.error) {
        DataError.Unauthorized -> println("请先登录")
        else -> println("加载失败:${result.error}")
    }
}

真实项目中,Unauthorized 应来自明确的鉴权协议或 HTTP 状态映射,不能由 UI 根据空数据自行推断。

与上一课的联系

上一课的 Repository 只展示了成功链路;本课补齐失败语义,使“数据源 → Repository → UI”的每一种结果都有明确类型和责任边界。