【问题标题】:How implement Arrow Kt with Android ViewModel?如何使用 Android ViewModel 实现 Arrow Kt?
【发布时间】:2020-03-26 11:41:11
【问题描述】:

在 Android 中,网络操作通常在 ViewModel 内完成。这可确保即使在重新创建 ActivityFragment 时(例如当设备旋转时),网络调用也会继续进行而不被取消。

现在将来自ViewModel 的网络请求的结果提交到视图(Activity/Fragment)。您有一个反应组件,例如 LiveDataObservable 来设置它的值。喜欢:

val resultLiveData = MutableLiveData<Result>()

fun doNetworkRequest() {
    repository.requestSomeResult()  // Assume this returns Arrow's IO
        .unsafeRunAsync { eitherResult ->
            eitherResult.fold({ error ->
                // Handle Error
            }, { result ->
                resultLiveData.value = result
            })
        }
}

我想知道是否有一种方法可以使 val resultLiveData = MutableLiveData&lt;Result&gt;() 不与特定的实现绑定,例如 LiveData 类似于返回 higher kindKind&lt;F, Result&gt;

有什么办法吗:

val result = Kind<F, Result>()

fun doNetworkRequest() {
    repository.requestSomeResult()  // Assume this returns Arrow's IO
        .unsafeRunAsync { eitherResult ->
            eitherResult.fold({ error ->
                // Handle Error
            }, { result ->
                resultLiveData.sendValue(result) // Or however it should be done
            })
        }
}

所以我可以稍后用我想要的实现来定义Kind&lt;F, Result&gt;

【问题讨论】:

    标签: android kotlin arrow-kt


    【解决方案1】:

    感谢您提出这个问题!这是我最近一直在做的事情。关于这点需要提几点:

    在接下来的几天里,我们将为 Arrow 发布一个 KotlinX 集成模块。我将允许你在 CoroutineScope 中定义你的 IO 任务。即:

    yourIOTask().unsafeRunScoped(scope) { cb -> }
    

    如果提供的范围被取消,这将确保您的 IO 任务被取消。这意味着您可以在本示例中使用视图模型范围来确定“视图模型”操作的范围,例如 doNetworkRequest,并且您将确保这些操作能够在配置更改后继续存在,并在视图模型发布时被取消。

    也就是说,如果我们观察 Android ViewModel 目前的工作方式,您仍然需要一个“中间层缓存”来交付结果,正如您所提到的,以确保始终交付这些结果,并且一旦视图开始观察我得到最新鲜的数据。通过将此机制与上一段中提到的范围相结合,您可以确保您的长期运行的任务将始终交付结果,无论它们是在配置更改之前、期间或之后完成的。

    从这个意义上说,如果您想继续使用 Android ViewModel,您可以使用箭头进行编码,例如:

    interface ViewStateCache<ViewState> {
    
      val cacheScope: CoroutineScope
    
      fun observeViewState(observer: LifecycleOwner, renderingScope: CoroutineScope, render: (ViewState) -> IO<Unit>): IO<Unit>
    
      fun updateViewState(transform: (ViewState) -> ViewState): IO<ViewState>
    }
    

    我们可以使用这个契约来确保以纯粹的方式使用 ViewModel。所有的 ViewModel 都可以实现这个契约,比如:

    class ViewModelViewStateCache<ViewState>(initialState: ViewState) : ViewModel(), ViewStateCache<ViewState> {
    
      override val cacheScope: CoroutineScope = viewModelScope
    
      private val _viewState = MutableLiveData<ViewState>(initialState)
      private val viewState: LiveData<ViewState> = _viewState
    
      override fun updateViewState(transform: (ViewState) -> ViewState) =
        IO {
          val transformedState = transform(viewState.value!!)
          _viewState.postValue(transformedState)
          transformedState
        }
    
      override fun observeViewState(observer: LifecycleOwner, renderingScope: CoroutineScope, render: (ViewState) -> IO<Unit>) =
        IO {
          viewState.observe(observer, Observer<ViewState> { viewState ->
            viewState?.let { render(it).unsafeRunScoped(renderingScope) {} }
          })
        }
    }
    

    这样,您就有效地拥有了一个使用 Android ViewModel 实现的视图状态缓存。这是一个实现细节,所以你可以注入它。您的程序将针对该接口工作。

    这里,ViewModel只作为一个缓存来传递结果,它是通过包装其操作来观察和更新视图状态到IO而变得纯粹。

    有了这样的东西,您可以拥有对您的表示和线程协调逻辑进行编码的纯函数,并将结果传递到上述缓存,例如:

    fun doNetworkRequest(): IO<Unit> = IO.fx {
          !viewStateCache.updateViewState { Loading }
          !repository.requestSomeResult().redeemWith(
            ft = {
              viewStateCache.updateViewState { ErrorViewState(ServerError) }
            },
            fe = { error ->
              viewStateCache.updateViewState { ErrorViewState(error) }
            },
            fb = { data ->
              viewStateCache.updateViewState { SuccessfulViewState(data) }
            }
          )
        }
    

    这些函数不需要存在于视图模型中,而是使用缓存作为委托来传递结果,因此可以将其作为实现细节注入

    您还需要在创建视图后立即开始观察视图状态缓存,这与您已经对视图模型所做的类似。请注意,我们有意将范围公开为缓存协定的一部分,以便您可以从外部访问它。

    这是一个示例,说明如何包装当前的 ViewModel api 以继续使用它们并确保它们的配置更改支持,主要用于逐步迁移到 Arrow。

    这种方法更像是一种方便的方法,可能需要一些改进,但应该可以。我们目前正在探索 Android 的常见问题,例如配置更改,以便通过扩展或类似的集成库为那些提供无缝体验。

    希望这足够有用,如果没有,请告诉我?

    【讨论】:

    • 我有点困惑。 createPlayer(username) 在哪里调用?为什么它必须返回 IO
    • 那是复制粘贴的剩余部分。将其重命名为 doNetworkRequest() 以匹配您的示例。那将是我们的表示逻辑,因此我们的视图将使用unsafeRunScoped(cacheScope) 来运行它。它返回一个IO&lt;Unit&gt;,因为它是我们纯IO程序的一部分,并将结果委托到视图状态缓存中(由视图模型实现),所以它不需要返回结果。
    • 我还是有点困惑。我可能在这里问的太多了..但是您可以添加视图层吗,在哪里以及如何调用observeViewState()?我真的很感激。非常感谢。
    • 我只是不确定doNetworkRequest() 将如何融入这一切。我希望看到视图层中的代码能帮助我理解一切。非常感谢。
    猜你喜欢
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-17
    相关资源
    最近更新 更多