Android Architecture Components: LiveData in Idiomatic Kotlin

Patrick Steiger
8 min readJun 29, 2019

Making use of Extension Functions, Sealed Classes and Generics to improve readability and reduce code repetition and verbosity.

In the last few years, Google's Android made big leaps in its software development environment. At Google IO 2017, two of the most significant changes to Android Development in recent times were announced: the introduction of Kotlin as an official programming language for the platform (alongside Java and C++), and the introduction of Android Architecture Components as a part of Android Jetpack — a set of libraries provided by Google to help developers follow a now officially recommended Model-View-ViewModel (MVVM) reactive app architecture, along other patterns. These two novelties perhaps represent the most game-changing experience in Android development since the introduction of Android Studio, and should help developers become more productive and deliver higher quality apps.

In this article, we'll show how to implement the LiveData component in an idiomatic Kotlin way through the use of exciting Kotlin features, such as extension functions, sealed classes and generics. To explore this, we'll assume a Use Case where the the actor (the app user) wants to list the users from a data source (in our case, Firebase Realtime Database, but you can adjust for your particular case accordingly).

Pre-requisites

We assume basic to intermediate knowledge in Kotlin, and intermediate knowledge in LiveData and its Transformations. For a LiveData introduction, I recommend reading the official documentation and this Medium post.

Implementation Goals

In a few words, our end goal is to:

  1. Embed states in our data to represent current data loading status — Success, Loading and Failure
  2. Concisely work with those states on the View side, keeping Activities and Fragments (which observe the data) as simple as possible.

Suppose we have the following data on our Firebase Realtime Database:

users
|__ Z5vGCGGdl5PDpftY5rdrV5NBTRq2
|__ name:
"Patrick Steiger"
|__ email: "psteiger@gmail.com"
|__ team: "Android Developer"
|__ z5vUp0ygNpN431hYD67jaHj1idN2
|__ name:
"Michelle Andrade"
|__ email:
"mandrade@gmail.com"
|__ team: "Android Tester"

Then we'd want to observe this data on our View the following way:

Let's build our way to achieve our goal!

Building our way from the Model to the View

We already have a good idea of what we'd like to use on the View side. Now we need to build our way from the Model to the View, a bottom-up approach (even though we already have our Top — the View :))

The Model

Our model is very simple and we'll use Kotlin's data class to represent it.

We'll also want to represent states of data loading for any of our models. Let's use a sealed class for this, let's call it Resource. Sealed classes are like Java's enum on steroids: among other things, they can hold state. And because we'd like to use the same Resource to encapsulate any other model we may have, we'll use a generic type.

What if we want to extract the data, if any, from a Resource, not caring whether it is Success (complete data) or Loading (partial data) or Failure (no data at all)?

Let's add a extractData value with no backing field for that. That would be the Java equivalent of writing a extractData() method that returns T, but in Kotlin we can simply make it a val with custom getter (and with no setter, as it is a val and not a var).

The LiveData

Our LiveData will use Firebase Realtime Database listeners to listen for changes on our database root. Our initial value upon LiveData creation will, of course, be Loading with no partial data to deliver yet.

Let's start with the general case. We wan't to build a LiveData that gets DataSnapshots of any kind of model.

Now we can build an UsersSnapLiveData upon this generic FirebaseResourceLiveData, passing the path of the database reference we want to listen to.

However, UsersSnapLiveData() will give us an LiveData<Resource<DataSnapshot>> and we actually want a LiveData<Resource<HashMap<String, User>>> to work with in the View, where the key of the HashMap is the key of the user on the database and the value is the proper User.

Let's begin by building an extension function that maps any LiveData of Resource<X> to a LiveData of Resource<Y>.

We now need to define the mapResource function that maps from Resource<T> to Resource<Y>. Because it is a function on Resource<T>, we can define it inside our Resource class instead of making it an extension function.

Now, which transform function can map from DataSnapshot to any POKO (Plain Old Kotlin Object :)) at all?

Now we can define our LiveData on the ViewModel!

The ViewModel

Android's ViewModel is the perfect place to put our LiveData because they survive configuration changes at runtime, meaning they'll stay alive and well after, for example, the user changes the phone orientation from vertical to horizontal.

Resultado de imagem para viewmodel lifecycle
ViewModel survives through activities recreation caused by device rotation

Traditionally, developers had to handle state saving and loading directly in Activities and Fragments through bundles: when a configuration change happens, Activities and Fragments are destroyed and recreated, and any data you keep on them are gone if not saved. To avoid losing data after configuration changes, developers had to save them on a bundle on onSaveInstanceState(), and restore them on onCreate(savedState: Bundle?) or onRestoreInstanceState(savedState: Bundle?). All of this is not needed anymore with the use of ViewModel!

Let's implement our ViewModel:

It may get repetitive to transform lots of Resources of DataSnapshot passing DataSnapshot::getTypedValue as a transform function. We can create a couple of — you guessed it, extension functions— to reduce the boilerplate even more.

Then, our ViewModel becomes:

The View

Finally, we want in an Activity or a Fragment to observe this transformed data, usersLiveData, to exhibit in a RecyclerView through an Adapter, for example.

We still have to define the onSuccess, onLoading and onFailure. They could be extension functions, but they also fit well inside our Resource class. Note that the functions will return the Resource object itself, so we can chain the function calls. Also note that we're not caring about the return of the lambda functions received by parameters — we just want them to have some side effect.

Here is the final Resource class:

Applying the “O” of the SOLID principles through the use of extension functions

SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable. SOLID principles were introduced by Martin in his 2000 paper Design Principles and Design Patterns, although the acronym itself was introduced later by Michael Feathers.

SOLID stands for: Single-responsibility principle, Open–closed principle, Liskov substitution principle, Interface segregation principle and Dependency inversion principle. They are all important principles in software architecture, but for the sake of this article, we do not mean to go through them all, except for the Open–closed principle: it is an interesting one that fits perfectly with the concept of Kotlin extension functions.

The open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”, that is, such an entity can allow its behaviour to be extended without modifying its source code. Kotlin’s extension functions allows us to easily implement behavior without altering the original class. Let’s see how can we rewrite our Resource.kt class by separating the classes from their behavior:

Note now how the Resouce<T> class now contains only its state description (that is, what it is), and all behavior (what it does) is now isolated from the state.

Hints

  1. Whenever you need to access your generic types inside your functions (e.g. you want to print logs that print the generic classes inside the transformations functions), use reified keyword on the generic types and make the functions inline. If your generic function receives other functions by parameter (e.g. transformation functions on map), make those crossinline. In fact, you might want to inline most of the time for performance reasons. Inline function calls are substituted, on compilation time, by their code block wherever they are called: it's as if you typed their code blocks directly every time instead of calling the function. Reified makes sure that the generic types are not erased on compilation time as they normally would. Read more at the official documentation.
  2. Android Studio might suggest that expliciting the types of the generic functions is unnecessary. However, I've incurred in compilation errors when making types implicit — the type processor failed to infer them.
  3. To avoid losing the latest valid data (if any) in the case of Resource.Failed, you'd need to alter the Resource sealed class by adding a latestValidData parameter of type T? and make small adjustments along the way: on FirebaseResourceLiveData, on the extension function onError, and on other Resource transformation extension functions.
  4. If you're working with Resource of lists of data (or other Iterable structures), you may find the map and filter versions of Resource useful:

Conclusion

Kotlin is a clean, modern language. Powered with functional programming features, and with a concise syntax, it fits nicely with the newly introduced LiveData/ViewModel components, that often require transformations on data.

I walk the talk, so I'm using the code I presented here on one of my apps, Freelapp — Freelancers Nearby. I plan on using LiveData across most of my other apps, like Hot Apps Nearby and Hot Music Nearby. All of my portfolio is already converted to, or already started at, Kotlin, and the productivity boost has certainly paid off.

If you like, follow me on GitHub, Twitter and LinkedIn!

Did you already have any extension function that you use on LiveData? Are you aware of any improvement or suggestion for the presented content?

Please share on comments below.

📝 Read this story later in Journal.

👩‍💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.

--

--

Patrick Steiger

Professional Android Developer. Android Dev Specialist @ Inter. Indie developer on free time. Passionate about programming. https://github.com/psteiger