Skip to content

Instantly share code, notes, and snippets.

@PhilippeBoisney
Last active April 7, 2019 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PhilippeBoisney/47b9eaee02df537cf7b0703707243a78 to your computer and use it in GitHub Desktop.
Save PhilippeBoisney/47b9eaee02df537cf7b0703707243a78 to your computer and use it in GitHub Desktop.
abstract class NetworkBoundResource<ResultType, RequestType>(private val coroutineScope: CoroutineScope) {
private val result = MediatorLiveData<Resource<ResultType>>()
private val supervisorJob = SupervisorJob()
init {
setValue(Resource.loading(null))
val dbSource = this.loadFromDb()
result.addSource(dbSource) {
result.removeSource(dbSource)
coroutineScope.launch(getErrorHandler() + supervisorJob) {
if (shouldFetch(it)) {
fetchFromNetwork(dbSource)
} else {
Log.d(NetworkBoundResource::class.java.name, "Return data from local database")
result.addSource(dbSource) { setValue(Resource.success(it)) }
}
}
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
Log.d(NetworkBoundResource::class.java.name, "Fetch data from network")
result.addSource(dbSource) { setValue(Resource.loading(it)) } // Dispatch latest value quickly (UX purpose)
coroutineScope.launch(getErrorHandler() + supervisorJob) {
result.removeSource(dbSource)
Log.e(NetworkBoundResource::class.java.name, "Data fetched from network")
val apiResponse = createCallAsync().await()
saveCallResults(processResponse(apiResponse))
result.addSource(loadFromDb()) { setValue(Resource.success(it)) }
}
}
fun asLiveData() = result as LiveData<Resource<ResultType>>
@MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) result.value = newValue
}
@WorkerThread
protected abstract fun processResponse(response: RequestType): ResultType
@WorkerThread
protected abstract suspend fun saveCallResults(items: ResultType)
@MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
@MainThread
protected abstract fun loadFromDb(): LiveData<ResultType>
@MainThread
protected abstract fun createCallAsync(): Deferred<RequestType>
private fun getErrorHandler() = CoroutineExceptionHandler { _, e ->
Log.e("NetworkBoundResource", "An error happened: $e")
result.addSource(loadFromDb()) { setValue(Resource.error(e, it)) }
}
}
class UserRepository(private val service: UserService,
private val dao: UserDao) {
fun getTopUsers(forceRefresh: Boolean = false, scope: CoroutineScope): LiveData<Resource<List<User>>> {
return object : NetworkBoundResource<List<User>, ApiResult<User>>(scope) {
override fun processResponse(response: ApiResult<User>): List<User>
= response.items
override suspend fun saveCallResults(items: List<User>)
= dao.save(items)
override fun shouldFetch(data: List<User>?): Boolean
= data == null || data.isEmpty() || forceRefresh
override fun loadFromDb(): LiveData<List<User>>
= dao.getTopUsers()
override fun createCallAsync(): Deferred<ApiResult<User>>
= service.fetchTopUsersAsync()
}.asLiveData()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment