Skip to content

Instantly share code, notes, and snippets.

@Ikhiloya
Last active May 29, 2020 14:38
Show Gist options
  • Save Ikhiloya/de8a49e488382f27599b7bc0d55afba0 to your computer and use it in GitHub Desktop.
Save Ikhiloya/de8a49e488382f27599b7bc0d55afba0 to your computer and use it in GitHub Desktop.
A generic class that defines the contract for pushing local data stored in {@link androidx.room.Room} database with a remote data source.The contract shows the various conditions that must be met before an [Entity] can be synchronized with the remote.
package com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.worker
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.dependency.Injector
import com.ikhiloyaimokhai.dependentworkmanagerperiodictask_kotlin.util.AppExecutor
import retrofit2.Call
import retrofit2.Response
import timber.log.Timber
/**
* A generic class that defines the contract for synchronization of local data stored in {@link androidx.room.Room} database with a remote data source.
* The contract shows the various conditions that must be met before an [Entity] can be synchronized with the remote.
* <p>
* It extends the {@link Worker} class that performs work synchronously on a background thread provided.
* The aim is to leverage the {@link Worker} to perform Periodic task of synchronization of local data with the remote data source
*
* @param <ResultType> This is the Generic Entity present in the local database
* @param <ResponseType> This is the Generic Response Entity from the remote
*/
abstract class WorkerContract<ResultType, ResponseType>(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
private var appExecutor: AppExecutor = Injector.provideExecutors()
override fun doWork(): Result {
return try {
val pendingItems: MutableList<ResultType> = loadPendingItemsFromDb()
when {
pendingItems.isNotEmpty() -> {
//loop through and check if conditions for posting are met
for (resultType in pendingItems) {
//checks if the Entity has a parent relationship
if (hasParentRelationship(resultType)) {
if (checkParentStatus(resultType)) //checks if the Parent Entity state is [SYNCED] or [PENDING] before posting
postToRemote(resultType) //posts the data to the remote if and only if all posting conditions have been met
} else {
//Entity does not have a Parent relationship, most likely it is a Parent to other Entities
postToRemote(resultType) //posts the data to the remote if and only if all posting conditions have been met
}
}
Result.success()
}
else -> {
//No pending items were found, so we return Result.retry() so it should be retried with backoff specified.
Timber.i("Pending items is empty for entity, retrying worker.....")
Result.retry()
}
}
} catch (ex: Exception) {
ex.printStackTrace()
//return FAILURE if there were errors
Timber.e(ex, "Error executing worker ")
Result.failure()
}
}
@Throws(java.lang.Exception::class)
private fun postToRemote(resultType: ResultType) {
val apiResponse: Response<ResponseType> = createCall(resultType).execute()
if (apiResponse.isSuccessful) {
appExecutor.diskIO().execute {
//process response if necessary
val processedResponse: ResponseType = processResponse(apiResponse)
//saves the result/data from network to db, this is done in a background thread
saveCallResult(processedResponse, resultType)
}
} else {
//posting was not successful, so we return failure
onPostFailed(resultType)
}
}
/**
* This queries the local database for items that are in the [pending] state
* It is these items that are eligible for posting
*
* @return List of pending items from the local database to be posted.
* The entities must be in the [pending] state
*/
protected abstract fun loadPendingItemsFromDb(): MutableList<ResultType>
/**
* This checks whether the Entity is a Parent or Child.
* It presupposes that the developer knows which Entity is a Parent or Child
* as such the concrete class returns the appropriate boolean
*
* @param resultType Entity to be checked
* @return [true] or [false] if the Entity has a child of a parent Entity or not
*/
protected abstract fun hasParentRelationship(resultType: ResultType): Boolean
/**
* This checks the Parent Entity to ascertain if has been synchronized with the remote.
* For Posting to occur, the Parent Entity must be in the [synced] state
*
* @param resultType Entity to be checked
* @return [true] or [false] if the Parent Entity is [synced] or not
*/
protected abstract fun checkParentStatus(resultType: ResultType): Boolean
/**
* This creates a call to post the local data to the remote
*
* @param resultType local data to be posted
* @return ResponseType from the remote
*/
protected abstract fun createCall(resultType: ResultType): Call<ResponseType>
/**
* This saves the result from the network operation to the database.
* It basically updates the existing local data state
*
* @param responseData the response data from the remote source
* @param localData the existing local data
*/
protected abstract fun saveCallResult(responseData: ResponseType, localData: ResultType)
protected open fun processResponse(response: Response<ResponseType>): ResponseType {
return response.body()!!
}
/**
* Called if you need to inform the user of the failure of the operation e.g to make a notification
*
* @param resultType
*/
protected open fun onPostFailed(resultType: ResultType) {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment