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.
- 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.
-
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
- 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
-
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
- Plugins
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)
}
- 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.
- This function takes in four argument parameters which are functions.
- 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.
- This is a suspend function, that loads data from your rest api and returns an object of
- This function returns RequestType.
- 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.
- 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.
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()
}
)
}