Best android open-source packages and libraries.

Candy Network Bound Resource

This project illustrates the use an algorithm (Network Bound Resource) for providing data to an app by either retrieving sufficiently recent data from a local cache, or loading the latest data from the network.
Updated 4 months ago

Candy

A relatively small and simple application that consumes this api which contains a list of candies and their prices. I built this demo following the MVVM architecture, Kotlin Flows, (Uni-directional data flow), dagger hilt, viewmodel, Room and Kotlin coroutines.

Network-Bound-Resource algorithm

  • The network bound resource is an algorithm that provides an easy function to fetch resource from both the database and the network. Depending on your needs, you can either :
    • Make a network request when there's no data in your cache.

    • Display data from your cache when there's no internet.

    • Display data from your cache as placeholder instead of a loading screen as you fetch fresh data from the internet.

You can easily achieve the above use cases using this algorithm, by making just a few adjustments. It works well with the Android architecture component.

Demo

  • When the app is first launched.Device has internet connection. Makes a network call and caches the data in the device.

  • When device is launched the second time. . Device has internet connection.

  • When the device does not have an internet connection. But there's data in cache.

  • When your logic requires the app re-do the network request, even though you have data already cached in the db. The data cached is displayed as a placeholder, while data is being loaded.

Tech-stack

  • Tech-stack

    • Kotlin - a cross-platform, statically typed, general-purpose programming language with type inference.
    • Coroutines - perform background operations.
    • Flow - handle the stream of data asynchronously that executes sequentially.
    • Dagger hilt - a pragmatic lightweight dependency injection framework.
    • Jetpack
      • Room - a persistence library provides an abstraction layer over SQLite.
      • LiveData - is an observable data holder.
      • Lifecycle - perform action when lifecycle state changes.
      • ViewModel - store and manage UI-related data in a lifecycle conscious way.
  • Architecture

    • Clean Architecture
    • MVVM - Model View View Model `
  • Gradle

    • Plugins
      • Ktlint - creates convenient tasks in your Gradle project that run ktlint checks or do code auto format.
      • Detekt - a static code analysis tool for the Kotlin programming language.
      • Spotless - format java, groovy, markdown and license headers using gradle.
      • Dokka - a documentation engine for Kotlin, performing the same function as javadoc for Java.
      • jacoco - a Code Coverage Library

Code

Network Bound Resource

inline fun <ResultType, RequestType> networkBoundResource(
    crossinline query: () -> Flow<ResultType>,
    crossinline fetch: suspend () -> RequestType,
    crossinline saveFetchResult: suspend (RequestType) -> Unit,
    crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow {

    //First step, fetch data from the local cache
    val data = query().first()

    //If shouldFetch returns true,
    val resource = if (shouldFetch(data)) {

        //Dispatch a message to the UI that you're doing some background work
        emit(Resource.Loading(data))

        try {

            //make a networking call
            val resultType = fetch()

            //save it to the database
            saveFetchResult(resultType)

            //Now fetch data again from the database and Dispatch it to the UI
            query().map { Resource.Success(it) }

        } catch (throwable: Throwable) {

            //Dispatch any error emitted to the UI, plus data emmited from the Database
            query().map { Resource.Error(throwable, it) }

        }

        //If should fetch returned false
    } else {
        //Make a query to the database and Dispatch it to the UI.
        query().map { Resource.Success(it) }
    }

    //Emit the resource variable
    emitAll(resource)
}

Explanation

This is a generic function

  • This is a generic function and that means it can work with any type of data,
  • ResultType is the data type loaded the local cache. Can be any thing, a list or any object.
  • RequestType is the data type loaded from the network. Can be any thing, a list or any object.

ARGUMENT PARAMETERS

  • This function takes in four argument parameters which are functions.

query

  • This is a function that loads data from your local cache and returns a flow of your specified data type
  • This function returns a Flow of ResultType.

fetch

  • This is a suspend function, that loads data from your rest api and returns an object of
  • This function returns RequestType.

saveFetchResult

  • THis is a function that just takes in (The data type got from the network) and saves it in the local cache.
  • This function returns Unit.

shouldFetch

  • This is a function returns a Boolean.
  • pass in a function that has the logic to whether the algorithm should make a networking call or not.
  • In this case, this function takes in data loaded from @param query and determines whether to make a networking call or not. This can vary with your implementation however, say fetch depending on the last time you made a networking call....e.t.c.

The repository

class MainRepository @Inject constructor(
    private val database: Appdatabase,
    private val apiService : ApiService,
) {

    private val weatherDao = database.charactersDao()

    fun getCandys() = networkBoundResource(
   
        // pass in the logic to query data from the database
        query = {
            weatherDao.getCandy()
        },
        // pass in the logic to fetch data from the api
        fetch = {
   
            //This is to show a progress bar
            delay(2000)
            apiService.getCandy()
        },

        //pass in the logic to save the result to the local cache
        saveFetchResult = { candys ->
            database.withTransaction {
                weatherDao.deleteAllCandy()
                weatherDao.insertCandy(candys)
            }
        },

        //pass in the logic to determine if the networking call should be made
        shouldFetch = {candys ->
            candys.isEmpty()
        }
    )
    
}
    

I hope you loved this