< Back

May 31st, 2020

How we manage the speed of the Cowrywise android app

{ Engineering }

How we manage the speed of the Cowrywise android app


Why should your android app speed matter?

I made a tweet some weeks back about how some mobile apps take long to load up. Asides the poor user experience, this can affect your bottom line.

In this article, I’ll show you how we upped Cowrywise android app speed. The app has tons of modules (Savings, Investments, Circles, Stash, Profile, etc). Hence, there’s a lot of data flows at any point in time. Given that, it won’t be fair to have a user wait for all pages to load before they make a specific action.

Android app speed starts with the SSOT (Single Source of Truth)

Simply put, the SSOT refers to having accurate data in one place. That way, any needed data is simply referenced from that source. This works better than having your data scattered around multiple places.

On the Cowrywise app, We always display the data from the app’s local database which lives on the user’s phone. Once the user logs in, We fetch recent (and most important) data in the background with the WorkManager API.


The WorkManager API makes it easy to schedule multiple tasks that are expected to run even if the app exits or device restarts.

Android Developer Documentation

That way, the load on the app’s interface thread is reduced. In turn, the user is able to switch between screens as fast as possible. Once the user gets to the page they are looking for, data required for that page would have already been fetched. We also offload certain one-off tasks to WorkManager.

The app fetches what is in the local database while displaying progress indicators/placeholders where necessary.

How we handle state changes

Using LiveData and Coroutines, We’re able to keep track of the loading, success and error states in the app. Data for UI is wrapped with a Resource class which has 4 states.

//Some Code Omitted for brevity
//Similar to this https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt
sealed class Resource<T>(
        val data: T? = null,
        val message: String? = null
)

class Success<T>(data: T) : Resource<T>(data)
class Failed<T> : Resource<T>()
class Loading<T>(data: T? = null) : Resource<T>(data)
class Error<T>(message: String?, data: T? = null) : Resource<T>(data, message)

fun <T, A> resourceLiveData(databaseCall: suspend () -> LiveData<T>,
                            networkCall: suspend () -> Response<A>,
                            saveResourceCall: suspend (A) -> Unit) =
        liveData<Resource<out T>>(Dispatchers.IO) {
            val source: LiveData<Resource<out T>> = databaseCall.invoke().map { Success(it) }
            val disposable = emitSource(source.map { Loading(it.data) })
            try {
                val response = networkCall.invoke()
                if (response.isSuccessful) {
                    disposable.dispose()
                    saveResourceCall.invoke(response.body()!!)
                    emitSource(databaseCall.invoke().map { Success(it) })
                } else {
                    var msg: String? = "Error occurred. Please try again later"
                    try {
                        msg = extractMessageFromJson(response.errorBody()!!.string())
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                    emit(Error(msg))
                    emitSource(source)
                }
            } catch (e: IOException) {
                emit(Failed())
                emitSource(source)
                Timber.e(e)
            } catch (e: Exception) {
                emit(Error(e.message))
                emitSource(source)
                Timber.e(e.message)
                e.printStackTrace()
             }
}

/**
* The code is used in the repository like this
* fun fetchAllPlans(userId: String) = resourceLiveData(
*   { plansDao.getAllPlans(userId) },
*   { client.getUserPlans(userId) },
*   { plansDao.insertAll(it.results) }
*   )
*   
*   The ViewModel calls this function.
*/

In the Fragment, we can then observe different states and update the UI respectively.

class AllPlansFragment : Fragment(R.layout.fragment_plans), PlanClickListener {

    private val plansViewModel by activityViewModels<PlansViewModel>()


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        //Some Code Omitted for brevity
        plansViewModel.plans.observe(viewLifecycleOwner, Observer {
            when (it) {
                is Success -> {
                    swipeContainer.isRefreshing = false
                    setupPlans(it.data!!)
                }
                is Loading -> {

                    if (!it.data.isNullOrEmpty()) {
                        swipeContainer.isRefreshing = true
                        setupPlans(it.data)
                    }else{
                        swipeContainer.isRefreshing = false
                        no_data_layout.visibility = View.GONE
                        loading_layout.visibility = View.VISIBLE
                    }
                }
                is Error -> {
                    loading_layout.visibility = View.GONE
                    swipeContainer.isRefreshing = false
                    it.message?.let { errorMessage ->
                        showErrorSnackBar(errorMessage)
                    }
                }
                is Failed -> {
                    swipeContainer.isRefreshing = false
                    loading_layout.visibility = View.GONE
                    childFragmentManager.showNoInternetDialog()
                }
            }

        })
    }
}

Placeholders and your android app speed

Sometimes your android app speed can be an illusion. When a task is going to take a while, there’s a need to inform the user without blocking interaction with other parts of the app. To do this, many make use of progress indicators and progress Dialogs. While these are good, they pose two problems:

  1. The user has no expectation of what is to come
  2. They are boring and plain

The combination of both problems can mess with the user’s mind. Yes, the process is taking time but it can feel longer with those. On the other hand, using skeleton screens, a mind game is played. As we move on, you’ll see how we have applied this approach.

Skeleton screens vs progress indicators

Loading appears faster to the user because the focus has shifted from loading progress to the content that is being loaded. Some examples are shown below

Contact loading screen
“Send Money to Cowrywise User Page” Loading Contacts
Plan loading screen
“Plans Page” Loading all the user plans for the first time

Still on Placeholders: Spinners vs Progress Dialogs

We also got rid of 95% of all ProgressDialogs in the Cowrywise android app (iOS also). Then, we replaced them with progress indicators as recommended by Google:

ProgressDialog is a modal dialog, which prevents the user from interacting with the app. Instead of using this class, you should use a progress indicator like ProgressBar, which can be embedded in your app’s UI. Alternatively, you can use a notification to inform the user of the task’s progress.

Android Developer Documentation

The login screen is an example of this. We show the progress indicator on the button. That way, the user can still interact with the app despite the loading process.

Cowrywise app login screen
Cowrywise app login page

Got questions for me? You can drop a comment or ask me on twitter. If you’d love to read more about my team’s work at Cowrywise, check out how we managed to keep the android app size small here.