A better way to pass Data from Api to Composable - Jetpack Compose

Saurabh Pant
Dev Genius
Published in
5 min readJul 4, 2022

--

Give an immunity booster does to our composables!

Not to surprise if you have used the api responses into the UI part of your apps directly. As Android developers, this is a common tendency. Though it is not a completely wrong practice but this is not even a good practice to practice. At times a reason given that api formats are meant to be for apps and so they can be used directly.

So, in this article we’ll try to understand one of the many ways to pass data received from an api to our composables effectively.

To understand this, let’s consider a scenario. Imagine if we have weather app screen and we’re displaying data as shown in the image below. Ignore the data conversions for sanity purposes. We’re simply displaying the data that we get.

App Screen displaying weather data

So when we started this app, we get a third party api integrated and we pass its response to our repository, view model and then on to UI. All good. No pain. But then after some time, the requirement came that we need to replace the api and use a new api with a completely new response. UI is not changed at all. Now what?

Most probably if we start making the changes as per the new response, majorly all of our layers will be modified because our api format is driving all of our layers, which is not at all expected. From an architecture point of view too, this is incorrect. So how to pass the data from api to our composables.

Data flow

Take the above flow from Api to UI.

  • We call our api via repository. The repository receives the exact response object from server and passes it to the use case.
  • The use case layer accepts the response object, converts it into a Domain Object, wrap it into a Resource object and forwards it to the view model.
  • The view model takes this Resource and update it into the screen state object.
  • The composable having the screen state object will recompose itself once the state updates.

Now take the same from UI to API.

  • The UI passes any event wrapped into an UIEvent object to the view model.
  • View model then process the computation as per the action done on UI. Here it’s the api call. It passes the domain object to use case.
  • The use case converts the domain object into the api request object and passes to the repository.
  • The repository then makes the api call with the request object.

Here, our use case maintains a contract between our UI and data layer. This contract keeps our UI unaffected in terms of api format and vice versa.

Let’s also talk about why are the following objects are present in our data flow ?

Resource<T>

The Resource object is basically a sealed class which looks like below.

This class is used to communicate between the use case and view model. Because view model will possibly updating the UI state, this class gives us a cleaner and more readable way to understand if we’ve received the success or an error.

UIEvent

Similar to Resource class, to keep our UI interactions clean with View models, we have UIEvent class which defines all the possible actions on UI by the user or otherwise required.

If you want to read details about why UIEvent class, do read my article on the same to get its root.

ScreenState

Screen state is basically a mutable state which contains a state for the data that our composable depends on to compose itself. This helps in managing the UI state in jetpack compose.

If you want to read more about how to manage UI state in Jetpack compose. Checkout my article on the same.

Back to Weather app scenario

Now that we understood how are we passing our data, let’s also look at the technical aspect of the data flow we’ve above in terms of our weather app scenario.

Starting from api side, our repository gets the response object and passes to use case where we’ve a flow created already and it would be emitting the data on each step as follows:

This flow is collected in our view model as follows:

And view model is updating the screen state object for each value received.

Updating our weather app to this set up and again considering the scenario where we already have an api integrated and now we’ve to update it with a new one with a new response format. Where do we need to make changes?

Well! With this set up, our api format changes are restricted from api to our use case in data flow. How? Because we’ve our contract here with UI in use case.

val result = apiResponse.body()
emit(Resource.Success(result.toWeatherData()))

Looking closely and we see that the result is converted to weather data object which is our contract object using the mapper classes before passing to the View models. We can convert any api response to our contract object in these mappers.

Bamn! Now our composable is independent of the api and its format used to get the data.

The main point to understand in this whole practice is that we should make changes only where they belongs and possibly not anywhere else. Though no architecture is ideal but we can eliminate the mess as much as we can at our end.

If you want me to make an article on something you want to understand, do let me know in comments.

If the articles are helpful to you, let’s connect on medium or on github.

Subscribe to email to get the notification for every new article.

See you again.

Until next time…

Cheers!

--

--

App Developer (Native & Flutter) | Mentor | Writer | Youtuber @_zaqua | Traveller | Content Author & Course Instructor @Droidcon | Frontend Lead @MahilaMoney