Você tem alguma idéia de como implementar o padrão de repositório com as rotinas de rede NetworkBoundResource e Kotlin? Sei que podemos lançar uma corotina dentro de um GlobalScope, mas isso pode levar ao vazamento de corotina. Gostaria de passar um viewModelScope como parâmetro, mas é um pouco complicado quando se trata de implementação (porque meu repositório não conhece um CoroutineScope de nenhum 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
fonte
fonte
suspend
funções ou retornarChannel
/Flow
objetos, dependendo da natureza da API. As reais rotinas são configuradas no modelo de exibição.LiveData
é apresentado pelo viewmodel, não pelo repositório.NetworkBoundResource
. Meus comentários são mais gerais: IMHO, uma implementação de repositório Kotlin deve expor APIs relacionadas a corotinas.LiveData
não possui o poder das corotinas RxJava ou Kotlin.LiveData
é muito bom para as comunicações da "última milha" da atividade ou fragmento e foi projetado com isso em mente. E para aplicativos pequenos, se você quiser pular repositórios e apenasViewModel
conversar diretamente com umRoomDatabase
, tudoLiveData
bem.Respostas:
A resposta @ N1hk funciona corretamente, esta é apenas uma implementação diferente que não usa o
flatMapConcat
operador (está marcada comoFlowPreview
neste momento)fonte
Atualização (2020-05-27):
Uma maneira que é mais idiomática para a linguagem Kotlin do que meus exemplos anteriores, usa as APIs de fluxo e empresta a resposta de Juan pode ser representada como uma função autônoma como a seguinte:
Resposta original:
É assim que eu tenho feito isso usando o
livedata-ktx
artefato; não é necessário passar em nenhum CoroutineScope. A classe também usa apenas um tipo em vez de dois (por exemplo, ResultType / RequestType), pois eu sempre acabo usando um adaptador em outro lugar para mapear esses tipos.Como o @CommonsWare disse nos comentários, no entanto, seria melhor apenas expor a
Flow<T>
. Aqui está o que eu tentei fazer para fazer isso. Observe que eu não usei esse código na produção, portanto, cuidado com o comprador.fonte
Flow
código irá fazer a solicitação de rede novamente quando os dados nas mudanças de banco de dados, eu postei uma nova resposta que mostra como lidar com issoflatMapConcat
bloco e também dentro doquery().map { Resource.Success(it) }
bloco, depois inseri um item no banco de dados. Somente o último ponto de interrupção foi atingido. Em outras palavras, a solicitação de rede não é feita novamente quando os dados no banco de dados são alterados.if (shouldFetch(data))
, verá que ele é chamado duas vezes. A primeira vez que você obtém os resultados do banco de dados e a segunda quandosaveFetchResult(fetch())
é chamada #Flow
. Você está salvando algo no banco de dados e deseja que o Room informe sobre essa alteração e chame seuflatMapConcat
código novamente. Você não estava usando umFlow<T>
e usandoT
em vez se você não quer que o comportamentoflatMapConcat
retornará um novo fluxo a ser observado, o fluxo inicial não será mais chamado. As duas respostas se comportam da mesma maneira, por isso manterei as minhas como uma maneira diferente de implementá-las. Desculpe a confusão e obrigado pela sua explicação!Eu sou novo na Kotlin Coroutine. Acabei de encontrar este problema esta semana.
Eu acho que se você seguir o padrão de repositório mencionado no post acima, minha opinião é livre para passar um CoroutineScope no NetworkBoundResource . O CoroutineScope pode ser um dos parâmetros da função no Repositório , que retorna um LiveData, como:
Passe o viewmodelscope do escopo interno como o CoroutineScope ao chamar getData () no seu ViewModel, para que o NetworkBoundResource funcione no viewmodelscope e seja vinculado ao ciclo de vida do Viewmodel. A corotina no NetworkBoundResource será cancelada quando o ViewModel estiver morto, o que seria um benefício.
Para usar o viewmodelscope do escopo incorporado , não se esqueça de adicionar abaixo em seu build.gradle.
fonte