What the Flows: Build an Android app using Flows, Live Data, and MVVM architecture
Knowledge of Retrofit, Room and Coroutines is a must for this guide.
In this tutorial We will build an app that fetches dog breeds from DogApi using Retrofit and then save it to database using Room. We will then use Flows to display the list of dogs from the database.
Let’s get Started! 🏁
First things first.
What are flows and when do you use one?
A flow is cold asynchronous data stream that sequentially emits values and completes normally or with an exception.
This basically means that you should use flows when there’s a stream of data to be emitted. This is very similar to Rx Java if you’re familiar to it.
Another question that might arise at this point now is that what is this stream of data?
Now requests (that you’d make from repository layer) are mainly of two types — One shot or Streams. To explain this I’ll use the same example as Google.
Think of a Tweet. Now the tweet itself is a One Shot Request. You only need to fetch it once and you’re done.You can easily do it using suspend functions. However the number of likes, retweets and comments are a stream of data. You’ll need to refresh them in a loop(say every 30 seconds) to get the updated value. This is the perfect situation to use flows.(More on this later)
Why not just use Live Data instead?
There’s nothing wrong in using live data. In your repository layer, you can always do —
However flows are much easier to use and are specifically built for this purpose. On top of that flows also have certain operators that make it a breeze to transform, combine flows and much more. They are just more powerful using very little code.
Start Building
On pressing the “Load More” button in the MainActivity ,we will make a network request to fetch a Dog breed using the Dog API. If the request is successful, we will then save it to the database. This is a One Shot Request.
In the MainActivityApi file, write this function.(ApiResponse is a wrapper class which we need to use for Response from DogAPI, Check here)
@GET("breeds/image/random")
suspend fun getRandomDogBreed(): ApiResponse<String>
Now in MainActivityRepository.kt, add this function:
safeApiCall is an extension function I created that handles HTTP Exceptions from the API and wraps the result in a ResultWrapper. If the API request is successful, we save it into the database and then return wrapped Result
at the end.
dog?.run { dogDao.save(this) }
Where dogDao
is an interface with a suspend function save:
Now call the above function from the ViewModel:
And now from the MainActivity.kt, set up an On Click listener which triggers the request and also observe the fetchedLiveData
for results. If you notice carefully I only use this to show the status of the request(Loading, No internet Connection) and not the fetched data.
Setting up Flows
In the DogDao.kt interface define this function:
@Query("SELECT * FROM dog")
fun loadAllDogsFlow(): Flow<List<Dog>>
Room 2.2 has out of the box support for flows. The above method will return a flow of lists of dogs. When the Load more button is pressed in the view, a new dog is added to the database and since the list of the dogs in the database is dynamic (stream), a flow is a perfect choice for this.
In another words we need to use flow in this case because the list of dogs in the database will keep updating.
Now call this method from MainActivityRepository.kt:
val dogListFlow = dogDao.loadAllDogsFlow()
Now in the MainViewmodel.kt, we need to convert this flow into Live data which we’ll then observe from the view:
asLiveData()
is an extension function that collects the flow and emits it as live data.
Now finally in the MainActivity.kt, observe the above live data:
Since this guide is specifically for learning flows, I am not adding code for adapters or layout. You can find the entire code at GitHub.
At this point you have have a working app that displays data from database (which is Single Source of Truth) using flows which can be updated by executing a one shot request by pressing the “Load More” button in the view.
Combining Flows
Now in RemoteDataSource.kt let’s set up another flow that emits a list of top (random actually) 20 dog breeds every two seconds(in a real life use case scenario this could be a network request):
Now in MainActivityRepositoty.kt, combine the above flow with the flow of list of dogs from the database using combine
operator:
Combine(){}
returns a flow whose values are generated with transform function by combining the most recently emitted values by each flow.- While
flowOn(context: CoroutineContext)
changes the context where the flow is to be executed and affects only preceding operators that do not have it’s own context. This basically means the functionapplyTopDogsToDatabaseList
extension function runs in theDispatchers.Default
thread because we used.flowOn(Dispatchers.Default)
. .conflate()
makes sure that emitter is never suspended due to a slow collector, but collector always gets the most recent value emitted.
And That’s it. ✅
You have now built an app that fetches a dog breed using a one shot request, saves it to the database and then displays the list of dogs in database using flows, combines it with another flow that emits every two seconds and then displays the list of dogs in the MainActivity.
The Top Dogs (which keeps changing every two seconds) are represented by pink background in the below GIF.
In Part 2 of this guide, We will implement a search bar to query the dog breeds using Channels and Flows. (Link Here)