¿Tiene alguna idea de cómo implementar un patrón de repositorio con las rutinas NetworkBoundResource y Kotlin? Sé que podemos lanzar una corutina dentro de un GlobalScope, pero puede conducir a una fuga de rutina. Me gustaría pasar un viewModelScope como parámetro, pero es un poco complicado cuando se trata de implementación (porque mi repositorio no conoce un CoroutineScope de ningún ViewModel).
abstract class NetworkBoundResource<ResultType, RequestType>
@MainThread constructor(
private val coroutineScope: CoroutineScope
) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
@Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
@MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
coroutineScope.launch(Dispatchers.IO) {
saveCallResult(processResponse(response))
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
coroutineScope.launch(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
android
kotlin
kotlin-coroutines
Kamil Szustak
fuente
fuente
suspend
funciones, o retornoChannel
/Flow
objetos, dependiendo de la naturaleza de la API. Las corutinas reales se configuran en el modelo de vista.LiveData
es introducido por el modelo de vista, no por el repositorio.NetworkBoundResource
. Mis comentarios son más generales: en mi humilde opinión, una implementación de repositorio de Kotlin debería exponer API relacionadas con las rutinas.LiveData
carece del poder de las rutinas RxJava o Kotlin.LiveData
es muy bueno para las comunicaciones de "última milla" a la actividad o fragmento, y fue diseñado con eso en mente. Y para aplicaciones pequeñas, si desea omitir repositorios y simplementeViewModel
hablar directamente con unRoomDatabase
,LiveData
está bien.Respuestas:
La respuesta @ N1hk funciona bien, esta es solo una implementación diferente que no utiliza el
flatMapConcat
operador (está marcada comoFlowPreview
en este momento)fuente
Actualización (2020-05-27):
Una forma que es más idiomática para el lenguaje de Kotlin que mis ejemplos anteriores, utiliza las API de Flow y los préstamos de la respuesta de Juan se pueden representar como una función independiente como la siguiente:
El código anterior se puede llamar desde una clase, por ejemplo, un repositorio, así:
Respuesta original:
Así es como lo he estado haciendo usando el
livedata-ktx
artefacto; No es necesario pasar ningún CoroutineScope. La clase también usa solo un tipo en lugar de dos (por ejemplo, ResultType / RequestType) ya que siempre termino usando un adaptador en otro lugar para mapearlos.Como dijo @CommonsWare en los comentarios, sin embargo, sería mejor exponer a
Flow<T>
. Esto es lo que he intentado hacer para hacer eso. Tenga en cuenta que no he usado este código en producción, así que tenga cuidado con el comprador.fuente
Flow
código hará la solicitud de red nuevamente cuando los datos en la base de datos cambien, publiqué una nueva respuesta que muestra cómo manejarlaflatMapConcat
bloque y también dentro delquery().map { Resource.Success(it) }
bloque, luego inserté un elemento en la base de datos. Solo el último punto de quiebre fue alcanzado. En otras palabras, la solicitud de red no se vuelve a realizar cuando cambian los datos en la base de datos.if (shouldFetch(data))
, verá que se llama dos veces. La primera vez que obtiene los resultados de la base de datos y la segunda cuandosaveFetchResult(fetch())
se llamaFlow
. Está guardando algo en la base de datos y desea que Room le informe sobre ese cambio yflatMapConcat
vuelva a llamar a su código. No estabasFlow<T>
usandoT
ay si no querías ese comportamientoflatMapConcat
devolverá un nuevo flujo para ser observado, por lo que ya no se llamará al flujo inicial. Ambas respuestas se comportan de la misma manera, por lo que conservaré la mía como una forma diferente de implementarla. Perdón por la confusión, y gracias por tu explicación!Soy nuevo en Kotlin Coroutine. Acabo de encontrarme con este problema esta semana.
Creo que si sigues el patrón de repositorio como se menciona en la publicación anterior, mi opinión es libre de pasar un CoroutineScope a NetworkBoundResource . El CoroutineScope puede ser uno de los parámetros de la función en el Repositorio , que devuelve un LiveData, como:
Pase el viewmodelscope de alcance incorporado como CoroutineScope al llamar a getData () en su ViewModel, de modo que NetworkBoundResource funcionará dentro del viewmodelscope y estará vinculado con el ciclo de vida del Viewmodel. La rutina en NetworkBoundResource se cancelará cuando ViewModel esté muerto, lo que sería un beneficio.
Para usar el alcance de vista del modelo incorporado , no olvide agregar a continuación en su build.gradle.
fuente