前言
毕竟是第一次在这上面写东西,感觉开头不写点什么东西不太好,不太严谨。那我就来个严谨的吧。在我们开发当中不可避免的会与数据打交道,数据可以来自network,local...因此很快就会想到一个模版:
kotlin
class XxxRep {
suspend fun getData(): Result<YourData>
}
class XxxUseCase(
private val xxRepImpl: XxxRep
) {
operator fun invoke() {
xxxRepImpl.getData()
///
}
}
有木有那种感觉了.接下来我们就要针对结果的success和failure去进行处理就行了,现在我要讲的就是包装这个Result。
a. sealed interface
这里先看一下代码
kotlin
sealed class Result<T>(val data : T?= null, val message: String? = null) {
data class Success<T>(val data: T?): Result<T>(data)
data class Error<T>(val message: String? = null, val data: T? = null): Result<T>(data, message)
}
是不是通用的密封类看起来觉得还行,但是我现在不用它了。理由无他,我不想写那么多的参数。开玩笑的,一方面有这理由,还有一方面就是我认为具体的message不应该是string直接在data层获取并一步步传递到domain层再到presentation层,我们应该包装成一种状态的形式用于表示error。大白话就是用个枚聚类表示error状态,然后定义些许个string。
csharp
sealed interface Error
typealias RootError = Error
sealed interface Result<out D, out E : RootError> {
data class Success<out D, out E : RootError>(val data: D) : Result<D, E>
data class Error<out D, out E : RootError>(val error: E) : Result<D, E>
}
我来解释一下👀 首先我定义了一个顶层Error,这意味着我们后续所有模块中的Error都是它的弟弟,哦不,儿子。
kotlin
sealed interface DataError: Error {
enum class NetWorkError : DataError {
NO_INTERNET,
TOO_MANY_REQUESTS,
UNEXPECTED_TOKEN,
PAYLOAD,
REQUEST_TIMEOUT
}
enum class LocalError: DataError {
SERIALIZATION,
UNKNOWN
}
}
sealed interface XxxUseCaseError : Error {
enum class FinalError : XxxUseCaseError {
ABC_ERROR,
CHUNK_ERROR
}
}
然后我用typealias取了个别名,这个没有啥特别含义啊,就是单纯命名以及泛型名称一样会报错罢了。 最后和之前的一样,定义了data,error并用out修饰,意思就是提前口头警告你小子这些个参数改不了,只能给爷乖乖读。
b. Data
核心已经结束了,剩下的就是怎么串联起来了,我们只要将上面的rep的改一下。
kotlin
interface AuthRep {
suspend fun registerUser(name: String?): Result<User, DataError>
}
class NetAuthRepImpl: AuthRep {
override suspend fun registerUser(name: String?): Result<User, DataError> {
return Result.Error(DataError.NetWorkError.NO_INTERNET)
}
}
class LocalAuthRepImpl: AuthRep {
override suspend fun registerUser(name: String?): Result<User, DataError> {
return Result.Error(DataError.LocalError.SERIALIZATION)
}
}
data class User(
val id: Int,
val name: String,
val email: String
)
剩下的大家懂得都懂了,我们只要在viewModel或者useCase中去使用when分别处理结果就行了。
scss
class RegisterUserUseCase(
private val netAuthRepImpl: AuthRep = NetAuthRepImpl(),
private val localAuthRepImpl: AuthRep = LocalAuthRepImpl()
) {
suspend fun registerUser(name: String) {
when(val result = netAuthRepImpl.registerUser(name)) {
is Result.Error -> { handleNetWorkError(result.error) }
is Result.Success -> TODO()
}
}
private fun handleNetWorkError(error: DataError) {
when(error) {
DataError.LocalError.SERIALIZATION -> TODO()
DataError.LocalError.UNKNOWN -> TODO()
DataError.NetWorkError.NO_INTERNET -> TODO()
DataError.NetWorkError.TOO_MANY_REQUESTS -> TODO()
DataError.NetWorkError.UNEXPECTED_TOKEN -> TODO()
DataError.NetWorkError.PAYLOAD -> TODO()
DataError.NetWorkError.REQUEST_TIMEOUT -> TODO()
}
}
到这里好像一下子就明朗起来了✌️,结束!
塔塔开
实在想不出来取什么标题了,最近刚又补完进击的巨人,就鬼使神差了。到这里我们再做一个扩展,其实对于我们来说,前期的努力是为了后期的装杯,因此到这里还没完。可以看到我的useCase中只用了一个netAuthImpl的方法,其实在我们实际场景中我们基本避免不了网络获取数据,本地存储数据这一个单向流操作。因此我们又很容易写出这样的:
kotlin
when(val result = netAuthRepImpl.registerUser(name)) {
is Result.Error -> { handleNetWorkError(result.error) }
is Result.Success -> {
localAuthRepImpl.registerUser(result.data.name)
}
}
MD确实好像也没啥问题,也不错,淦,那要不结束了吧。怎么可能,我这个例子比较简单罢了。我想要的是一种简洁明了跟读手册一样啊くそやろ!
kotlin
inline fun <D, E : RootError, R> Result<D, E>.andThen(block: D.() -> Result<R, E>): Result<R, E> {
return when (this) {
is Result.Error -> {
Result.Error(this.error)
}
is Result.Success -> {
block(this.data)
}
}
}
inline fun <D, E : RootError> Result<D, E>.onSuccess(action: (value: D) -> Unit): Result<D, E> {
if (this is Result.Success) {
action(this.data)
}
return this
}
inline fun <D, E : RootError> Result<D, E>.onFailure(action: (error: E) -> Unit): Result<D, E> {
if (this is Result.Error) {
action(this.error)
}
return this
}
fun Result<*, DataError>.asUnitText() {
if (this is Result.Error) {
when (this.error) {
DataError.LocalError.SERIALIZATION -> "Ooops, There is some error when serializing the data!"
DataError.LocalError.UNKNOWN -> TODO()
DataError.NetWorkError.NO_INTERNET -> TODO()
DataError.NetWorkError.TOO_MANY_REQUESTS -> TODO()
DataError.NetWorkError.UNEXPECTED_TOKEN -> TODO()
DataError.NetWorkError.PAYLOAD -> TODO()
DataError.NetWorkError.REQUEST_TIMEOUT -> TODO()
}
} else {
throw Exception()
}
我扩展了Result方法,很简单,简单的让我误以为自己很厉害了。
kotlin
return netAuthRepImpl.registerUser(name).andThen {
localAuthRepImpl.registerUser(this.name)
}.onSuccess {
// do works
}.onFailure {
handleNetWorkError(it)
}
简洁明了吧,over~😏
最后
其中可能涉及了一些架构术语,像domain,usecase诸如此类,其实这是来自整洁架构的定义。有兴趣的朋友可以了解一下,我简单介绍一下,核心就是将整个结构分为三部分,数据,操作,显示三个域,每个域互相隔离。真特么简单,这个介绍😄(我自己先吐槽一下)。
作为一个老看客了,距离上一次写技术博客还是疫情年上大学的时候,我也不知道为什么突然写了,想做就做了,可能人就是这样的生物吧。
少年时你听到人们说应该欣赏日落,但你忙于探索世界,不能静静地坐下来观赏。再后来,你抗拒本应该感觉得到的情感,斥之为庸俗。随着年龄的增长,你不再关心日落在别人眼中的是否庸俗。你用自己的眼睛去看它,内心充满感激和欣喜。