Skip to content

Instantly share code, notes, and snippets.

@kyryloz
Created May 2, 2019 10:34
Show Gist options
  • Save kyryloz/8a7eab2644a75247c6c7ef6f48faa9c1 to your computer and use it in GitHub Desktop.
Save kyryloz/8a7eab2644a75247c6c7ef6f48faa9c1 to your computer and use it in GitHub Desktop.
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
abstract class NetworkBoundResource<ResultType, RequestType> @MainThread constructor() :
LiveDataResource<ResultType> {
private val result = MediatorLiveData<Resource<ResultType>>()
private val tag = javaClass.simpleName
@MainThread
override fun launch(): LiveDataResource<ResultType> {
Timber.tag(tag).d("Launch")
result.value = Resource.loading()
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData, Resource.Freshness.CACHED))
}
}
}
return this
}
@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.status) {
Resource.Status.SUCCESS -> {
GlobalScope.launch(Dispatchers.IO) {
Timber.tag(tag).d("save to DB: ${printData(response.data)}")
saveCallResult(response.data!!)
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
Timber.tag(tag).d(
"success: ${printData(newData)}"
)
setValue(Resource.success(newData, Resource.Freshness.LATEST))
}
}
}
}
Resource.Status.ERROR -> {
result.addSource(dbSource) { newData ->
Timber.tag(tag).d("post error: ${response.message}")
setValue(Resource.error(response.message, newData))
}
}
else -> {
Timber.tag(tag).d("Not sure how to process response: $response")
}
}
}
}
private fun printData(data: Any?): String {
val string = data?.toString()
return string?.subSequence(
0,
if (string.length >= 30) 30 else string.length
)?.toString()?.plus("...") ?: "<no data>"
}
override fun asLiveData() = result as LiveData<Resource<ResultType>>
@WorkerThread
protected abstract suspend fun saveCallResult(data: RequestType)
@MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
@MainThread
protected abstract fun loadFromDb(): LiveData<ResultType>
@MainThread
protected abstract fun createCall(): LiveData<Resource<RequestType>>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment