Haben Sie Ideen, wie Sie ein Repository-Muster mit NetworkBoundResource- und Kotlin-Coroutinen implementieren können ? Ich weiß, dass wir mit einem GlobalScope eine Coroutine starten können, aber dies kann zu einem Coroutine-Leck führen. Ich möchte ein viewModelScope als Parameter übergeben, aber es ist etwas schwierig, wenn es um die Implementierung geht (da mein Repository kein CoroutineScope eines ViewModel kennt).
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
quelle
quelle
suspend
Funktionen oder RückgabeChannel
/Flow
Objekte verfügbar machen, abhängig von der Art der API. Die eigentlichen Coroutinen werden dann im Ansichtsmodell eingerichtet.LiveData
wird vom Ansichtsmodell eingeführt, nicht vom Repository.NetworkBoundResource
. Meine Kommentare sind allgemeiner: IMHO sollte eine Kotlin-Repository-Implementierung Coroutinen-bezogene APIs verfügbar machen.Antworten:
@ N1hk Antwort funktioniert richtig, dies ist nur eine andere Implementierung, die den
flatMapConcat
Operator nicht verwendet (es ist alsFlowPreview
in diesem Moment markiert )quelle
So habe ich es mit dem
livedata-ktx
Artefakt gemacht; Sie müssen kein CoroutineScope übergeben. Die Klasse verwendet auch nur einen Typ anstelle von zwei (z. B. ResultType / RequestType), da ich immer einen anderen Adapter verwende, um diese zuzuordnen.Wie @CommonsWare in den Kommentaren sagte, wäre es jedoch schöner, nur a freizulegen
Flow<T>
. Hier ist, was ich versucht habe, um das zu tun. Beachten Sie, dass ich diesen Code in der Produktion nicht verwendet habe. Käufer sollten also aufpassen.quelle
Flow
Code wird die Netzwerkanforderung erneut stellen, wenn sich die Daten in der Datenbank ändern. Ich habe eine neue Antwort gepostet, die zeigt, wie man damit umgehtflatMapConcat
Block und auch in denquery().map { Resource.Success(it) }
Block eingefügt und dann ein Element in die Datenbank eingefügt. Nur der letztere Haltepunkt wurde getroffen. Mit anderen Worten, die Netzwerkanforderung wird nicht erneut gestellt, wenn sich die Daten in der Datenbank ändern.if (shouldFetch(data))
Sie sehen, dass er zweimal aufgerufen wird. Das erste Mal, wenn Sie die Ergebnisse aus der Datenbank erhalten, und das zweite Mal, wennsaveFetchResult(fetch())
es aufgerufen wirdFlow
. Sie speichern etwas in der Datenbank und möchten, dass Room Sie über diese Änderung informiert und IhrenflatMapConcat
Code erneut aufruft. Sie haben nicht einFlow<T>
undT
stattdessen verwendet, wenn Sie dieses Verhalten nicht wollenflatMapConcat
gibt einen neuen zu beobachtenden Fluss zurück, sodass der anfängliche Fluss nicht mehr aufgerufen wird. Beide Antworten verhalten sich gleich, daher werde ich meine als eine andere Art der Implementierung beibehalten. Entschuldigen Sie die Verwirrung und vielen Dank für Ihre Erklärung!Ich bin neu bei Kotlin Coroutine. Ich bin diese Woche auf dieses Problem gestoßen.
Ich denke, wenn Sie sich für das im obigen Beitrag erwähnte Repository-Muster entscheiden, können Sie meiner Meinung nach ein CoroutineScope an die NetworkBoundResource übergeben . Das CoroutineScope kann einer der Parameter der Funktion im Repository sein , die LiveData zurückgibt, wie:
Übergeben Sie den integrierten Bereich viewmodelscope als CoroutineScope, wenn Sie getData () in Ihrem ViewModel aufrufen , damit NetworkBoundResource innerhalb des viewmodelscope funktioniert und an den Lebenszyklus des Viewmodels gebunden ist. Die Coroutine in der NetworkBoundResource wird abgebrochen, wenn ViewModel tot ist, was von Vorteil wäre.
Um die Build-in Bereich zu verwenden viewmodelscope , vergessen Sie nicht unten in Ihrem build.gradle hinzufügen.
quelle