web-dev-qa-db-ja.com

Kotlinコルーチンを使用したNetworkBoundResource

NetworkBoundResource およびKotlinコルーチンを使用してリポジトリパターンを実装する方法について何かアイデアはありますか? GlobalScopeを使用してコルーチンを起動できることはわかっていますが、コルーチンのリークにつながる可能性があります。 viewModelScopeをパラメーターとして渡したいのですが、実装に関しては少しトリッキーです(私のリポジトリは、任意のViewModelのCoroutineScope)。

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))
                    }
                }
            }
        }
    }
}
8
Kamil Szustak

Kotlin Coroutineは初めてです。今週、この問題に遭遇しました。

上記の投稿で述べたようにリポジトリパターンを使用する場合、私の意見ではCoroutineScopeNetworkBoundResourceに自由に渡すことができます。 CoroutineScopeRepositoryの関数のパラメーターの1つであり、次のようなLiveDataを返します。

suspend fun getData(scope: CoroutineScope): LiveDate<T>

ViewModelでgetData()を呼び出すときに、組み込みスコープviewmodelscopeをCoroutineScopeとして渡すため、NetworkBoundResourceviewmodelscopeおよびViewmodelのライフサイクルにバインドされます。 ViewBが無効になると、NetworkBoundResourceのコルーチンがキャンセルされます。これはメリットになります。

組み込みスコープviewmodelscopeを使用するには、build.gradleに以下を追加することを忘れないでください。

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'
0
Freddie