【问题标题】:How to make sealed classes generic in kotlin?如何在 kotlin 中使密封类通用?
【发布时间】:2022-05-12 22:53:21
【问题描述】:

是否可以使用下面的 AsyncResult 类来防止在 UserDataAppResult 和 CreateUserResult 中重新定义 InFlight、Error 和 InFlight?

//TODO: use this to make the below classes generic?
sealed class AsyncResult{
  object InFlight : AsyncResult()
  data class Error(val errorMessage: String) : AsyncResult()
  data class Loaded<out T>(val users: T) : AsyncResult()
}

sealed class UserDataAppResult : AppResult() {
  object InFlight : UserDataAppResult()
  data class Error(val errorMessage: String) : UserDataAppResult()
  data class Loaded(val users: List<User>) : UserDataAppResult()
}

sealed class CreateUserResult : AppResult() {
  object InFlight : CreateUserResult()
  data class Error(val errorMessage: String) : CreateUserResult()
  data class Loaded(val users: User) : CreateUserResult()
}

上面的代码有可能是这个样子吗?

sealed class AsyncResult{
  class InFlight : AsyncResult()
  data class Error(val errorMessage: String) : AsyncResult()
  data class Loaded<out T>(val users: T) : AsyncResult()
}

sealed class UserDataAppResult : AsyncResult()
sealed class CreateUserResult : AppResult()

val activeUsers: Flowable<UserDataAppResult> = appDatabase.userDao().getActiveUsers(appSettings.currentLanguage.ordinal)
    .map<UserDataAppResult> { UserDataAppResult.Loaded(it) }
    .onErrorReturn { UserDataAppResult.Error(it.localizedMessage) }
    .startWith(UserDataAppResult.InFlight)
    .observeOn(AndroidSchedulers.mainThread())
    .share()

fun createUser(): Flowable<CreateUserResult> {

  val userId = UUID.randomUUID().toString()
  val user = User()
  user.id = userId
  return appDatabase.userDao().insertAll(user)
      .map <CreateUserResult> { CreateUserResult.Loaded(user) }
      .onErrorReturn { CreateUserResult.Error(it.localizedMessage) }
      .startWith(CreateUserResult.InFlight)
}

目前未找到有意义的 UserDataAppResult.Error。 但是是否可以重用 AppResult 密封类层次结构并引入新类型。

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    您的 Object 在 Kotlin 中不能有泛型类型,但这可以通过以下示例解决:

    sealed class ResponseState<out T> {
        object Loading : ResponseState<Nothing>()
        data class Error(val throwable: Throwable) : ResponseState<Nothing>()
        data class Success<T>(val item: T) : ResponseState<T>()
    }
    

    写作:

    val _state = MutableLiveData<ResponseState<MessageModle>>() 
    
    
    _state.postValue(ResponseState.Loading)
    
    myNetworkCall { response, e
      if (e != null) _state.postValue(ResponseState.Error(e))
      else _state.postValue(ResponseState.Success(response))
    }
    

    阅读:

    state.observe(..., {state -> 
      when(state) {
        Loading -> showLoading()
        is Error -> showError(state.throwable)
        is Success -> onSuccess(state.item)
      }
    }
    

    【讨论】:

    • 如何使用它?请举个例子
    • 更新:想通了
    • 你是怎么用的?
    • 有关受@kosh 启发的完整解决方案,请参阅我的回答:stackoverflow.com/a/69695797/2267723
    【解决方案2】:

    这在 Kotlin 中是不可能的。您使用的每种类型都必须在某处具有显式声明的类。即使在超类中声明嵌套类的情况下,编译器也不会隐式创建类。

    对于您的问题,我建议您将代码从组合两个基于继承的层次结构重写为结合继承和组合的两个之一,或者只是以某种方式重构层次结构,例如(我假设结果的确切实例如果不是Loaded,则与您无关):

    sealed class AsyncResult {
        object InFlight : AsyncResult()
        data class Error(val errorMessage: String) : AsyncResult()
        sealed class Loaded<out T>(val result: T) : AsyncResult() {
            sealed class UserDataAppResult(users: List<User>) : Loaded<List<User>>(users)
            sealed class CreateUserResult(user: User) : Loaded<User>(user)
        }
    }
    

    【讨论】:

      【解决方案3】:

      通过 Google 指南:https://developer.android.com/jetpack/docs/guide

      sealed class Resource<T>(
              val data: T? = null,
              val message: String? = null
      ) {
          class Success<T>(data: T) : Resource<T>(data)
          class Loading<T>(data: T? = null, var refreshing: Boolean = false) : Resource<T>(data)
          class Error<T>(data: T? = null, message: String) : Resource<T>(data, message)
      }
      

      【讨论】:

        【解决方案4】:

        受@kosh 解决方案的启发

        -> 视图状态:

        sealed class ViewState<out T> {
            object Loading : ViewState<Nothing>()
            data class Error(val throwable: Throwable) : ViewState<Nothing>()
            data class Success<T>(val item: T) : ViewState<T>()
        }
        

        -> 在 ViewModel 中:

        private val _homeVS = MutableLiveData<ViewState<HomeMode>>()
        val homeVS: LiveData<ViewState<HomeMode>> get() = _homeVS
        
        // start requesting API 
        _homeVS.value = ViewState.Loading
        try {
            val result = loadData()
            _homeVS.value = ViewState.Success(result)
        } catch (e: Exception) {
            _homeVS.value = ViewState.Error(e)
        }
        

        然后你可以在布局/视图中使用这个泛型

        -> 视图:

        viewModel.homeVS.observe(viewLifecycleOwner, {state ->
                when(state) {
                    is ViewState.Error -> showError(state.throwable)
                    is ViewState.Success -> onSuccess(state.item)
                    ViewState.Loading -> showLoading()
                }
            })
        

        -> 在布局上我们可能需要一点点调整

        sealed class ViewState<out T> {
            object Loading : ViewState<Nothing>()
            data class Error(val throwable: Throwable) : ViewState<Nothing>()
            data class Success<T>(val item: T) : ViewState<T>()
        
            fun toData(): T? = (this as? Success)?.item
        }
        

        toData 仅在成功时提供数据

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text='@{vm.homeVS.toData() != null ? vm.homeVS.toData().param1 : ""}' />
        
            <!--onLoading-->
            <View
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility='@{vm.homeVS instanceof ViewState.Loading ? View.VISIBLE : View.GONE}' />
        
            <!--onError-->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility='@{vm.homeVS instanceof ViewState.Error ? View.VISIBLE : View.GONE}' />
        

        当然,使用 BindingAdapter,您可以让它变得更好。这里只是为了说明解决方案。

        祝你好运,'。

        【讨论】:

        • 值得注意的是,观察的时候不要使用this@Fragment,而是使用fragment的viewLifecycle。
        • @kosh thnx??。更新了..我个人使用数据绑定来观察变化
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-08-05
        • 2021-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多