Tag: Kotlin

Android – App restructuring part 2 – Kotlin networking

In my first post about modernizing my native app, I mixed both Java and Kotlin. I wanted a 95% or more Kotlin application; with that unless the app isn’t complex, that app has to use Kotlin coroutines. I started working on an application that would call my already existing library that would get the data. While I was able to do this, I wasn’t able to get down to exactly why things were working the way that they were doing so when examining the log. Because of that, I decided to go a bit more basic and create a even more simplified app that would get data from a RESTful-like source and show the result. I didn’t care about the data, other than it was an actual network call; it needed to take real time to perform and had the capability of being interrupted or turned off. I went out looking for another API to use. I found an API out there which would cover these bases which is free and doesn’t need API keys: Quotable. I got the idea from this article.

An observation on Android Studio – given the Android architectural guidelines, I thought that a new app would scaffold out a generic app with view, view model and repository, it doesn’t – certainly an interesting choice not to do that. In any event, I created the app: a Retrofit 2 helper, a quote service, a quote repository to handle getting those quotes, the quote models, the activity, view model and view model factory.

My code is here at github.

Kotlin coroutine

What is a coroutine anyway?

A coroutine is an instance of suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.

Coroutines can be thought of as light-weight threads, but there is a number of important differences that make their real-life usage very different from threads.

Coroutines basics, retrieved 17-Jun-22

Unlike Java, where one would create a threaded class extension to perform long running or network based activity, in Kotlin, you can create a coroutine to do the same type of work, excepting without the pain of setting up and running your threaded work. This will generally make development easier and faster; but there are some things to keep track of and I’ll point them out. I’d recommend reading this article on coroutines with Android specifics.

I’ll not be going over my coroutine code within my app, as there are some pretty good articles out there on coroutines including from Jetbrains here, and another from Android / Google.

Network status check

The first thing I wanted to handle is gaining / loosing network connection. This has been changed / deprecated over the various API versions with the latest change under API 29. I came up with two different ways to do that check.

  1. Context extension. I’m using this to set the initial network status in the view model from the activity. I found this Stack Overflow article which had the basis for the extension solution.
  2. ConnectivityManager callback helper. I’ve used a BroadcastAdapter for this in the past, but that’s been partially deprecated, so time to move to a ConnectivityManager. I found a good blog entry on this. There’s a slight problem with the blog entry version, but I’ve fixed it in my implementation. The error with their implementation is that the same network can be added multiple times to the network list; not so with my version. This uses LiveData for communication, which is fine, as a Android context is required for the call requiring access to either the activity or the Application context. This is the code from the activity that deals with the handling of the network state helper:
       NetworkStatusHelper(this).observe(this, {
            val status = it == NetworkStatus.Available

            when(it) {
                NetworkStatus.Available -> {
                    setNetworkText(true)
                }
                NetworkStatus.Unavailable -> {
                    setNetworkText(false)
                    if (viewModel.getDataFetchStarted() &&
                        !viewModel.getDataFetchCompleted() &&
                        !viewModel.getDataFetchNetworkCompleted()
                    ) {
                        viewModel.cancelJob()
                    }
                }
            }
        })

LiveData vs. Kotlin Flow

Given we are talking about LiveData and Kotlin Flow, let’s define what they are:

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

Android Jetpack, retrieved 17-Jun-22

In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value. For example, you can use a flow to receive live updates from a database.

Flows are built on top of coroutines and can provide multiple values. A flow is conceptually a stream of data that can be computed asynchronously. The emitted values must be of the same type. For example, a Flow<Int> is a flow that emits integer values.

Kotlin flows on Android, retrieved 17-Jun-22

I’ve been reading up on activities, view models and repositories and I came across these blog entries: Don’t use LiveData in Repositories and No more LiveData in Your Repository: There are better options. What they point out does intuitively make sense; especially if you plan to create unit tests – you don’t want to tie Android architecture components to a non-Android class. However, there are some things to consider on switching over from a LiveData to a Flow. I’d like to emphasize with Flows – while it’s in the documentation here, it didn’t quite register for me until later:

Flows are cold streams similar to sequences — the code inside a flow builder does not run until the flow is collected.

Asynchronous Flow, retrieved 17-Jun-22

For more background on Flow, I recommend this article that was written by the Flow’s author and is a good read; for how Flow and coroutines work together, read this article.

From Quote repository:

    @WorkerThread
    fun initializeDataSource() {
//        job = scope?.launch {
        scope?.launch {
            networkResults = getData()
        }
    }

The reason for the commented out line is that essentially, the Flow variable (networkResults) contains nothing from the getData fun (method), as it has not been collected / consumed. While you can get the job from the launch, the job’s isCompleted status will be true almost immediately and thus not worth bothering with here. To interrogate the job’s isCompleted and other statuses within the Flow, you must assign it when the Flow actually starts to be collected.

In this app, where the networkResults is collected / consumed is in the view model, via the startLiveDataCollection fun which calls the collectData fun that processes the emit‘s or exceptions generated in the quote repository’s getData fun. This loads whatever we got back – data, error or an exception – into a LiveData variable bound from the activity. This activity code that deals with the resulting LiveData starts with this line:

viewModel.networkResults.observe(this, {

Due to how things work in Android, I’d like to point out two things. The startLiveDataCollection and collectData fun’s are run on the main thread, as UI items should only be updated on the main thread. The network fetch Flow in the quote repository’s getData fun is run on a worker (in this particular case the Dispatcher.IO) thread for two reasons:

  • On Android networking cannot be done on the main thread
  • Since this work can take a long time, the processing should be moved off of the main thread to allow the main thread to continue to smoothly update the UI as well as avoid UI jank

I’d recommend annotating your fun’s using the appropriate threading model so that the compiler can examine your code and throw a warning / error, if it is incorrectly used.

Lets go over the Flow from the quote repository:

            job = currentCoroutineContext().job

            val quotesService = RetrofitHelper.getInstance().create(QuotesService::class.java)

            for (netTry in 1..3) {
                try {
                    yield()
                    for (i in 5 downTo 1) {
                        networkFetchComplete = false

                        results = quotesService.getQuotes(i)

                        if (results != null && results.isSuccessful) {
                            emit(NetworkResult.Success(results))
                        } else {
                            emit(NetworkResult.Failure(results))
                        }

                        delay(5000);
                        yield()
                        networkFetchComplete = true
                    }
                } catch (uhe: UnknownHostException) {
                    emit(NetworkResult.Exception(uhe))
                    // cancel the flow
                    currentCoroutineContext().cancel()
                } catch (io: IOException) {
                    emit(NetworkResult.Exception(io))
                    // cancel the flow
                    currentCoroutineContext().cancel()
                } catch (e: Exception) {
                    throw e
                }

                if (networkFetchComplete) {
                    break
                }
            }

At the very top the assignment of the class job variable is done from the currentCoroutineContext‘s job, so that the job status in the view model can be properly interrogated to see what is going on. As I want this to be cancelable, I’ve got yield sprinkled through here to add places where it can be cancelled. As with other types of coroutine commands, cancellation is cooperative so placement of commands, such as yield, where it can be cancelled is required. The Flow then tries to get the data from the quote service and if it doesn’t throw, it will emit the successful response with data or not successful response with no data. If it throws, then for known exception types, it will emit the exception for a general exception, it will re-throw it. If it gets a UnknownHostException or IOException it will cancel itself. A twist on this, if exception catch job cancels are commented out and the app will either (dependent on timing):

  • Network Status helper from the main activity is triggered with no network available. It then calls the cancel job in the quote repository through the view model. This is most likely from a timing perspective.
  • Will emit the exception on the flow and go back to the view model and activity, show the error and then bullet #1 will be hit.

I’ve got the delay in there to allow me to turn off the emulator’s networking to see what happens – otherwise the networking part normally too fast for just one network call. I’d like to have moved some of the duplicate emit‘s from those exception handlers out of this to a single fun, but when I did so, the emit‘s show as an error in that fun; therefore I’m stuck with same boilerplate littered throughout. The networkFetchComplete variable dealt with if the code was able to make it all of the way through the 5 quote fetches and if so, drop out of the outer loop and allow the view model to check it’s status.

What Did I learn?

The goal of this app is to learn how to get data and process it. In that, the goal was achieved and ready to transfer that knowledge back to obtaining data for my application. Dealing with flows is / will be tougher than just the coroutine networking and I think that will be where the challenge lies. My application library, already written in Java already does the heavy work with the networking, and original processing of the data; I’ll need to properly assimilate that processing into a repository. That is the next step in the app refactoring.

What would be next steps for this app – if it was continued?

If I was expanding this to experiment further, I’d deal with these areas:

  • In the quote repository, in the netTry outer loop of getData fun, I’d add additional logic to handle the specific network failures to delay / network check for starting / continuing of the quotes
  • Given that I wasn’t able to start / continue with the first bullet, to await the network reconnection and start the processing.
  • Add a local and remote data source in the repository, so that it would be the same as the Android architectural model

Wrap up

Hopefully this gave you some insight into what and how I went about learning about Kotlin coroutines and Flows. I didn’t find an app example quite like this, so hopefully it will be of use to you.