AndroidPub
Published in

AndroidPub

350% performance improvement in fetching data from the server, by using RxJava

Fetching data, from 14 seconds to 4 using RxJava

How I improved the times of fetching data from the server, from 14 seconds to 4 seconds? By using RxJava! 🐇

Fetching data from different API calls, and displaying the aggregated data in a list, is a common pattern in mobile development.

Below is an example from Udemy’s app:

  • Step 1: fetch the list of the user’s courses from the server
  • Step 2: for each course, invoke a different API call to fetch the “completed percentage” information for this user
Two steps for displaying aggregated data in a RecyclerView

Often, a proxy server will be responsible for aggregating the data, making the two steps process transparent for the mobile app.

But, occasionally, a proxy server isn’t part of the technology stack, such as when using Firebase, or in cases of limited development resources. In these cases, the app is directly responsible for aggregating the data.

Cache recently fetched data

Another common pattern is to cache the recently fetched data, and in the following requests, load the data from the local database, instead of fetching from the server again.

Sharing my experience

Recently, I published an Android app of an Augmented Reality game, which is an Augmented Reality version of the classic Hide and Seek game.

In this post, I’m going to share my experience of developing the app, emphasizing two topics:

  • Fetching data from different API calls, and displaying the data aggregated in a RecyclerView, using RxJava
  • Cache recently fetched data in a local database, using Retrofit and Room

The app’s main feature is loading virtual models from the server, like the octopus and lighthouse above, and displaying a list of these models to the user. The user then selects virtual models and creates an Augmented Reality world.

Important notice: knowledge in ARCore isn’t mandatory, as Augmented Reality isn’t the main subject of this blog post.

The video below is a demonstration for fetching the virtual models from the server. To allow the user to place a virtual model in the real world, two different API calls need to be performed.

I used Poly API twice, as the source for the virtual models. First, fetching the virtual models’ data, and then, another API call for fetching the virtual models’ rendering files.

ProgressBar vs Reactive programming

Fetching the data and files of a single 3D virtual model takes about 1–2 seconds. Each list of virtual models consists of 7–15 models, hence fetching an entire list of virtual models can take longer than 14 seconds.

I had two possible approaches as to fetching and displaying the list of virtual models:

  1. To display a ProgressBar while fetching the models, and display the augmented reality Activity, with the model’s list, only when the entire list’s data had been fetched.
  2. To display the augmented reality Activity immediately, display the models' list from the start, and update the list as new virtual models are fetched.

In the first option, the user can start playing only after the entire list had been fetched, about 15 seconds. In the second option, the user can start playing almost immediately, and new virtual models are added continuously.

I chose the second approach, and the feature’s objectives were:

  • Fetch virtual models from the server using Retrofit.
  • Store the virtual models' data and files using Room. In the following requests, load the models from the database, instead of fetching from the server again.
  • Display the virtual models list using PagedListAdatper and RecyclerView.
  • Not displaying a progress bar. Instead, show the list of virtual models from the start, and update the list as a new model is fetched. Use RxJava to achieve this reactive programming approach.
  • Use a ThreadPool to parallelize the data fetching work

First take — working with Transformations

As part of the Android Architecture Components, Google developed Transformations.switchMap(), a powerful tool for converting a source LiveData into a different one, and doing so while observing the source LiveData values.

Here is the main code section implemented using Transformations:

I quickly learned that Transformations isn’t powerful enough for my needs, it’s missing necessary capabilities. The video below is a demonstration for fetching the virtual models. Notice the frame drops, and duplicates in the list. A detailed explanation for the reason for the performance issues will be given at the end of this post.

Second take — working with RxJava

I discovered Transformations was not strong enough, missing necessary tools, so I turned to RxJava.

Before we dive into the implementation using RxJava, notice in the video below how quickly the list of virtual models is loaded, and without any frame drops.

The four data loading steps

So it’s time to start examining the reactive programming implementation, and we will start with the ViewModel, that connects between the Activity and the Repository, in keeping with the MVVM architecture pattern:

Important note

If you look back at the Transformations implementation code, and compare it to the above RxJava’s implementation, you’ll notice the codes are pretty similar. The main difference is the distinct() method, at line 15. This is, perhaps, the most important line in this post, and the reason for the performance differences between the first and second loading videos. I will elaborate on this method later.

If you are short in time, feel free to jump to “The most important line” section. You will jump directly to the climax, missing the fun part of getting there. 😎

Four steps

Fetching the data is divided into four steps. Each step can be performed only after the previous step execution had been finished. I will first go over the steps briefly, and then expand about each step separately.

Step 1 — new Models

Insert new Models to the database, using Room. This step happens when invoking loadModelsFlowable() at line 13, more about this later.

Step 2 — loadPoly()

Fetch the virtual model’s data when model.shouldGetPoly() is true. Using Retrofit to perform a GET request to Poly API.

The input is the virtual model id, from step 1. The response is saved to the database. An example of a JSON response:

Step 3 — downloadModel()

Download the virtual model’s files needed to render the model in an Augmented Reality manner. This step occurs when model.shouldDownloadModel() is true.

Step 3 is dependent on the result of step 2. At step 3, we take the URL files’ links from lines 9, 15, and 20, and download the files.

The model’s files are written to the internal file storage, and the file’s URI is written to the database.

The Model class is a simple POJO:

Step 4 — initAugmentedModel()

When the virtual model’s data and files (steps 2 and 3) are fetched and stored, model.canRender() will be true, and we can render the virtual model.

Step 1 — new Models (diving in)

The Jetpack guideline to app architecture recommends using a single source of truth pattern. The main idea behind this pattern is to designate a single source, usually a local database, from which all the Views (as in MVVM) load the data. When new data is fetched from the server, the database source is updated. In this approach, the Views observe changes in the database, instead of the server directly.

The AugmentedRealityActivity observes changes to modelList, which is a RxJava’s Flowable object, and defined in AugmentedRealityViewModel. The AugmentedRealityActivity code:

If you recall, my first attempt was to use Transformations and LiveData.

Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.

Because I’m working with RxJava’s Flowable, which isn’t lifecycle-aware, at line 35 we should clear the subscription.

modelList is a Flowable object, hence, every time values in the database are changed, the modelsAdapter will be notified with the updated list of models.

ModelsRepository’s responsibility is to fetch from the network using PolyService, to store the results using ModelDao, and by that complying with the single source of truth pattern.

Note line 20. Here we create new temp Models, and insert them into the database, with a single attribute — the virtual model’s id. This is the initial step. In the following steps, these Models’ attributes will be updated when new data is fetched.

When the user enters AugmentedRealityAcityivity, immediately, placeholders views are displayed in the RecyclerView. This is achieved by inserting the temp Models into the database, when the list is loaded.

Note insertIgnoreExists() also at line 20. A virtual model can be part of multiple lists, so we might have already fetched data for some of the models. We want to re-use previous network results, instead of fetching the same data twice. To do so, we ignore cases in which the model’s id already exists in the database. Note OnConflictStrategy.IGNORE at line 9.

Step 2 — loadPoly()

At the end of the first step, we have a Model in the database for each model id in the list. In the second step, we execute a GET request to fetch the model’s name, thumbnail URL and the model’s rendering files URLs.

Some interesting points to note here:

  • The GET request runs on separate threads, supplied by AppExecutors (lines 5 and 21). More about this later.
  • PolyService.getPoly() method returns a Single RxJava’s base class (line 7). Instead, I could have used Flowable or Observable, but as this is a single item flow, Single is most suitable here.
  • At line 24 we update the Model’s data in the database.
  • Updating values in the database triggers a notification to all subscribers. As we saw earlier, in AugmentedViewModel we defined a subscriber:
modelsRepository.loadModelsFlowable(modelIds)               .subscribe(model -> {}); // complete code at the beginning of the article

The PolyServie interface, auto-implemented by Retrofit:

And the Poly class:

Step 3 — downloadModel()

Similar to the previous step, this step uses PolyService to download the files necessary to render the virtual model. Then, stores the result to the database using ModelDao.

The most important line

Finally, it’s time to talk about the single line that made the performance differences between the implementation using Transformations, and using RxJava (jump back to the videos at the top of the post).

Recall the performance issues at the first take? Examining the logs reveals the reason, but first, let's look at the logs of the implementation using RxJava:

Here you see the expected behavior when opening the app for the first time. Model’s 4Gkg2wLGJ_p data and files weren’t previously fetched, hence shouldGetPoly() is true. What do we do? We load the model’s data, “loading poly”. When the data is fetched, shouldGetPoly() is false, and now shouldDownloadModel() is true. So, we download the model’s files. Finally, when the files are fetched, shouldDownloadModel() is false, and we can render the model.

Now, let's take a look at the logs using Transformations and switchMap():

After the model’s data is fetched, shouldGetPoly() is false, and shouldDownloadModel() is true. So far so good. The next step is to download the model’s files. We are still on track.

But then, we see “downloading model 4Gkg2wLGJ_p” again, and again. Believe me, I only captured a small part of the log. It actually took more than 14 seconds until the downloading of the entire list of models was finally completed. In the implementation using RXJava and distinct(), the same list, with the same 4 virtual models, was downloaded in merely 4 seconds.

So, what happened here?

downloadModel() method was invoked for the first time for model 4Gkg2wLGJ_p. As loadPoly() and downloadModel() are both methods invoking asynchronous requests to the server, several seconds elapse by the time the response is returned. In the meantime, other Models’ data is fetched, so the database is updated with the new data. When the database values are updated, all the Flowable’s subscribers are notified, exactly as expected. In our case, the subscriber at AugmentedRealityActivity is notified.

To summarize the last section — in the time period of invoking downloadModel(), and waiting for the response, the database is updated by other models data. As every change to the database invokes the subscribe(), and given that model.shouldDownloadModel() is still True for 4Gkg2wLGJ_p (as the download has not yet been completed), this triggers more calls to downloadModel(), until model.shouldDownloadModel() returns False. So, in this time period, the files of 4Gkg2wLGJ_p are downloaded multiple times.

At this point, this is where distinct() and RxJava becomes really handy. I’m using distinct() as a “bridge”, or a “bypass”, if you will, for the time period between invoking loadPoly() and downloadModel(), and the return of the responses.

What does distinct() actually do?

Distinct returns a Flowable that emits all items emitted by the source Publisher that are distinct according to a key selector function

And a key selector function is:

keySelector — a function that projects an emitted item to a key value that is used to decide whether an item is distinct from another one or not

Understanding the solution

So here comes the climax — line 8.

By concatenating model.id, to values representing the current state of the model in the “four data loading steps”, we prevent all those redundant calls to downloadModel().

The first time downloadModel() is invoked, The value of model.shouldGetPoly()is false, and the value of model.shouldDownloadModel() is true. This means keySelector will return “4Gkg2wLGJ_pfalsetruefalse”.

Until the files of the model are fetched, the keySelector’s return value will remain the same. Therefore, downloadModel() is called for model 4Gkg2wLGJ_p only once.⛰️

Final touches

Did you notice that the server requests, and database operations, are done on background threads? This is AppExecutors:

Implementing PagedListAdapter was similar to implementing RecyclerView.Adapter, except you should supply DiffUtil.ItemCallback:

And finally, the model’s layout, using Data Binding:

Conclusion

For me, it was an extremely educating feature, that exposed me to the Reactive Programming world. I hope the post was clear and detailed, and like me, you see now the strength of RxJava.

Thank you for reading!

If you enjoyed this post, please click the 👏👏👏 button and share, to help others find it!

By the way, won’t you enjoy a Hide and Seek game 🧐 with an Augmented Reality twist? You can download the app right here:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store