Flower
Flower is an Android library that makes networking and database caching easy. It enables developers to fetch network resources and use them as is OR combine them with local database at single place with fault tolerant architecture.
Why Flower?
- It helps you to handle different states (
Loading,Success,Error) of resources efficiently. - It helps you to use local data in case of network unavailability.
- It provides a fluid app experience by not blocking the
main threadwhen accessing network/database resources.
You can find companion medium article here
Installation
Add Gradle dependency as below
dependencies {
implementation("io.github.hadiyarajesh:flower:2.0.0")
}
Usage
Sample model class
data class MyModel(
val id: Long,
val data: String
)Prerequisite
- Return type of
RoomDAO function must beFlow<MyModel>(Only if you're caching network resources using a local database)
@Dao
interface MyDao {
@Query("SELECT * FROM Data")
fun getLocalData(): Flow<MyModel>
}- Return type of
Retrofitapi interface function must beFlow<ApiResponse<MyModel>>
interface MyApi {
fun getRemoteData(): Flow<ApiResponse<MyModel>>
}
1. Add FlowCallAdapterFactory as CallAdapterFactory in Retrofit builder
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addCallAdapterFactory(FlowCallAdapterFactory.create())
.build()
2. In Repository
2.1. If you wants to fetch netwrok resources and cache into local database, use networkBoundResource() higher order function. It takes following functions as parameters
- fetchFromLocal - It fetch data from local database
- shouldFetchFromRemote - It decide whether network request should be made or use local data
- fetchFromRemote - It fetch data from network
- processRemoteResponse - It process response of network request. (e.g., save response headers)
-
saveRemoteData - It saves result of network request (
MyModel) to local database - onFetchFailed - It perform provided action when network request fails (e.g., Non HTTP 200..300 response, exceptions etc)
fun getMyData(): Flow<Resource<MyModel>> {
return networkBoundResources(
fetchFromLocal = { myDao.getLocalData() },
shouldFetchFromRemote = { localData -> localData == null },
fetchFromRemote = { myApi.getRemoteData() },
processRemoteResponse = { },
saveRemoteData = { myDao.saveMyData(it) },
onFetchFailed { errorMessage, statusCode -> }
).flowOn(Dispatchers.IO)
}OR
2.2 If you only wants to fetch network resources, use networkResource() higher order function
fun getMyData(): Flow<Resource<MyModel>> {
return networkResource(
fetchFromRemote = { myApi.getRemoteData() },
onFetchFailed { errorMessage, statusCode -> }
).flowOn(Dispatchers.IO)
}
3. In ViewModel
Collect/transform Flow to observe different state of resources (Loading, Success, Error)
sealed class UiState<out T> {
object Empty : UiState<Nothing>()
object Loading : UiState<Nothing>()
data class Success<out T>(val data: T?) : UiState<T>()
data class Error(val data: String?) : UiState<Nothing>()
}private val _myData: MutableStateFlow<UiState<MyModel>> = MutableStateFlow(UiState.Empty)
val myData: StateFlow<UiState<MyModel>> = _myData
init {
viewModelScope.launch {
getMyData()
}
}
suspend fun getMyData() = repository.getMyData().collect { response ->
when (response.status) {
Resource.Status.LOADING -> {
_myData.value = UiState.Loading
}
Resource.Status.SUCCESS -> {
_myData.value = UiState.Success(response.data)
}
Resource.Status.ERROR -> {
_myData.value = UiState.Error(response.message)
}
}
}
4. In Activity/Fragment
Observe view model data in Activity/Fragment/Composable function to decide UI changes
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.myData.collect { data ->
when (data) {
is UiState.Loading -> {
// Show loading
}
is UiState.Success -> {
// Show success
}
is UiState.Error -> {
// Show error
}
else -> { }
}
}
}
}Sample
Sample app is provided in this repository. It fetch random quote from remote api and save it to local persistent database in order to display it on UI.