Skip to content

duttabhishek0/NetworkBound-OfflineCaching

Repository files navigation

NetworkBound-OfflineCaching

platform License

Warning

This project is no more maintained. Expect some minor bugs/ compatibility issues

An android app to demonstrate offline caching capabilities offered by JetPack Libraries.
Link to Geeksforgeeks article.

The Jetpack article describes a way to provide data from a web service or retrieve data from an offline storage(if available). This repository contains the files required to make an application which can demonstrate such capabilities.

Output

Before starting, lets see how our app would look:

The Algorithm

The block diagram represents the control flow :

NetworkBoundResource

The goal here is to minimize the changes in the user-expereince. Important things to be noted:

  • For the first time, when the application is started, the data is fetched from the webservice(restAPI, AWS, etc) in a background thread.
  • As soon as the data fetch is done, all the data is stored in the local database, using the ROOM persistence library
  • Parallelly, the data is exposed and shown in the activity/ fragment.
  • If any change is there in the data, then the whole view is not refreshed, rather it is fetched in the background thread, laterwards the new data is replaced with the old data

The app follows the MVVM architecture.

The core part of this application is the NetworkBoundResource.kt file, where the magic happens.

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

    val flow = if (shouldFetch(data)) {
        emit(Resource.Loading(data))

        try {
            saveFetchResult(fetch())
            query().map { Resource.Success(it) }
        } catch (throwable: Throwable) {
            query().map { Resource.Error(throwable, it) }
        }
    } else {
        query().map { Resource.Success(it) }
    }

    emitAll(flow)
}
  1. The above code first checks if there is any requirement for fetching the data or not.
  2. .
    • If it is required to fetch the data, then it is emitted.
    • Else just look into the map
  3. Kotlin Flow is used here


Application

This repository contains code for an android application, which basically shows a list of data fetched from a random API generator, using RetrofitAPI using which APIs are converted into callable objects.

The following data are required to be fetched and shown in the activity.

Code
// Data Class to store the data
@Entity(tableName = "cars")
data class CarList (
    @PrimaryKey val make_and_model : String,
    val color: String,
    val transmission : String,
    val drive_type : String,
    val fuel_type : String,
    val car_type : String
)

A repository will be used. Repository pattern is one of the design patterns that available out there.A Repository is defined as a collection of domain objects that reside in the memory.

DAO for the API path

Code
interface CarsDao {
    @Query("SELECT * FROM cars")
    fun getAllCars() : Flow<List<CarList>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertCars(cars : List<CarList>)
    
    @Query("DELETE FROM cars")
    suspend fun deleteAllCars()
}

Repository class to centralize the data access

Code
class CarListRepository @Inject constructor(
    private val api : CarListAPI,
    private val db : CarListDatabase
) {
    private  val carsDao = db.carsDao()

    fun getCars() = networkBoundResource(
        query = {
            carsDao.getAllCars()
        },
        fetch = {
            delay(2000)
            api.getCarList()
        },
        saveFetchResult =  { CarList ->
            db.withTransaction {
                carsDao.deleteAllCars()
                carsDao.insertCars(CarList)
            }
        }
    )
}

This thing has to be implemented in a viewModel from which data will be exposed on a view or a fragment. The view model can get data from the repository by observing it's live data.

Code
@HiltViewModel
class CarListViewModel @Inject constructor(
    api : CarListAPI
) : ViewModel() {

    private val carListLiveData =  MutableLiveData<List<CarList>>()
    val carList : LiveData<List<CarList>> = carListLiveData

    init {
        viewModelScope.launch {
            val carList = api.getCarList()
            delay(2000)
            carListLiveData.value = carList
        }
    }
}

Adding a repository between the data source and a view is recommended by Android, as it seperates the view, so that focus can be put seperately on increasing the UI of app and the database. Moreover, the repository helps by centralising the data access, which directly reduces the boilerplate code.

The app uses MVVM Architecture

Contributing contributions welcome

STEPS:-
  1. Go to https://github.com/duttabhishek32/NetworkBound-OfflineCaching fork repo.(It's very easy, just follow the steps).
  2. Now click on the "*Issues*" and see which issues you can work on.
  3. Open the "*database.cpp*" file and then click on the edit button and then do the changes.
  4. Click on the "*Commit changes *", and then click on the "*Create Pull Request*" button.
  5. I will then reveiw the PR(Pull Request) and if I found it to be correct I will merge it in main.

About

This is a sample car list Android Application built to demonstrate the use of clean architecture.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages