Как реализовать Arrow Kt с Android ViewModel? - PullRequest
1 голос
/ 26 марта 2020

В Android сетевые операции обычно выполняются в пределах ViewModel. Это гарантирует, что даже когда Activity или Fragment воссоздается (например, когда устройство поворачивается), сетевой вызов продолжается и не отменяется.

Теперь для отправки результата сетевого запроса от ViewModel к виду (Activity / Fragment). У вас есть реактивный компонент, такой как LiveData или Observable, чтобы установить значение для него. Например:

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<Result>() не привязываться к конкретной реализации c, такой как LiveData, что-то вроде возврата higher kind, Kind<F, Result> вместо.

Есть ли способ, которым я мог бы сделать:

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<F, Result> позже с реализацией, которую я хочу?

1 Ответ

3 голосов
/ 26 марта 2020

Спасибо за этот вопрос! Это то, над чем я работал в последнее время. Несколько замечаний по этому поводу:

В следующие дни мы опубликуем sh модуль интеграции KotlinX для Arrow. Я позволю вам охватить ваши задачи ввода-вывода в CoroutineScope. Т.е.:

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

Это обеспечит отмену ваших задач ввода-вывода в случае отмены предоставленной области. Это означает, что вы могли бы охватить ваши операции «модели представления», такие как doNetworkRequest в этом примере, используя область действия модели представления, и вы будете уверены, что они выдержат изменение конфигурации и будут отменены, когда модель представления будет выпущена.

Сказал, что, и если мы посмотрим, как Android ViewModels работает в данный момент, вам все еще понадобится "кэш среднего уровня", чтобы доставлять результаты, как вы упоминаете, чтобы гарантировать, что эти результаты всегда доставляются и как только вид начинает наблюдаться, я получаю самые свободные sh возможные данные. Имея этот механизм вместе с областью, упомянутой в предыдущем параграфе, вы можете гарантировать, что ваши долгосрочные задачи всегда будут приносить результаты, независимо от того, были ли они выполнены до, во время или после изменения конфигурации.

В в этом смысле, и если вы хотите продолжать использовать 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>
}

Мы могли бы использовать этот контракт, чтобы убедиться, что ViewModels используются в чистом виде , Все ViewModels могут реализовать этот контракт, например:

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) }
        }
      )
    }

Эти функции не нужно было бы жить в модели представления, но вместо этого использовать кеш в качестве делегата для доставки результатов, поэтому он может быть внедрен как деталь реализации .

Вам также понадобится начать наблюдение за кэшем состояния представления, как только представление будет создано, так что это будет похоже на то, что вы уже делали с вашими моделями представления. Обратите внимание, что мы намеренно представили область действия как часть контракта на кэш, чтобы вы могли иметь к ней доступ извне.

Это пример того, как можно обернуть текущий API ViewModel, чтобы продолжать использовать его и поддержка их изменений конфигурации, в основном для постепенного перехода на Arrow.

Этот подход больше похож на удобный подход и может потребовать некоторой доработки, но должен работать. В настоящее время мы находимся в процессе изучения Android обычных проблем, таких как изменения конфигурации, чтобы обеспечить беспрепятственный доступ для тех, кто через расширения или подобное, в библиотеку интеграции.

Надеюсь, что это было достаточно полезно, если нет, дайте мне знать ?

...