Skip to content

Offline Capabilities

Overview

App performance can be improved by displaying data more faster, and here is when the local database come in.
When retrieving data from API, the data will automatically stored to database and the database will work as a single point of truth, which means only the data from database will be displayed to the presentation layer. Let's take a look at this diagram:

offline graph

It will work as our guide for implementing offline capabilities, where we will have two data sources (API and DB).

Implementation

We will be using RemoteLocalSync, one of the feature of this module. Let's say we have an API to get list of developer, how do we implement retrieving that data using offline capabilties, let's take a look:

DeveloperDataStore.kt
class DeveloperDataStore(
    web: DeveloperApi,
    dao: DeveloperDao,
    private val pref: DevPreferenceManager
) :
    DeveloperRepository {

    // Data sources
    override val webService = web
    override val dbService = dao

    override fun getDeveloper() =
    // DeveloperResponse is return type from API
        // List<DeveloperEntity> is return type from DB
        object : RemoteLocalSync<DeveloperResponse, List<DeveloperEntity>>() {

            override fun shouldFetch(data: List<DeveloperEntity>) = data.isEmpty() // (1)

            override fun saveToDB(data: List<DeveloperEntity>) {
                data.forEach(dbService::save) // (2)
            }

            override fun loadFromDB() = dbService.getDeveloper() // (3)

            override fun loadFromAPI(): Single<DeveloperResponse> = // (4)
                webService.getDeveloper(pref.getString(Variables.USER_TOKEN, ""))
                    .lift(singleApiError())

            override fun syncData(
                dataFromAPI: DeveloperResponse,
                dataFromDb: List<DeveloperEntity>
            ): List<DeveloperEntity> {
                val result = dataFromAPI.listDeveloper?.map { it.toDeveloperEntity() }
                return result?.minus(dataFromDb) ?: emptyList()
            } // (5)

        }.asObservable() // (6)
}
  1. Define if fetching data to API is needed
  2. Define how to save data to DB
  3. Define how to load data from DB
  4. Define how to load data from API
  5. In this method, two things to do:
    • Map data from API to DB
    • Configure new data to store to DB and to display to presentation layer
  6. We're using Observable because there might be two data to emit

Then, to keep cached value even when there's exception. add delayError in ViewModel when observing like this:

someViewModel.kt
useCase.getDeveloper()
    .compose(observableScheduler(delayError = true))
    .subscribe(
        { result.success(it) },
        { genericErrorHandler(it, result) }
    ).let(disposable::add)