Modeling ViewModel States Using Kotlin’s Sealed Classes

Introduction

“Sealed classes are used for representing restricted class hierarchies…”

As such, Sealed Classes are useful when modeling state’s within various app workflows

We’ve leveraged this to simplify a few different use cases in our app, and wanted to share what we’ve found about modeling ViewModel states using sealed classes.

Our Problem

We commonly implement an MVVM pattern for new screens. Generally, we create a ViewModel class and use DataBinding to bind our ViewModel data into the UI.

This works great for persistent UI state changes where the ViewModel state and UI state stay in lock step until something acts on the view model.

But what about 1-off events? Things that don’t persist over time, or don’t necessarily represent a new UI state?

For us, these tend to fall into a couple of categories

  • transient UI messages (usually shown as a Toast or Snackbar)
  • handling requests to move to and from screens

We’ve found that we can model these workflows as simple state machines, and kotlin’s sealed classes can help

Our Solution

We’ve identified several state types that we then enumerate using Sealed Classes. We subscribe to these events within a when(state){...} expression and handle each state appropriately.

Using Kotlin’s sealed classes helps

  • define and encapsulate these states
  • allow us to create instances of these states with their own data
  • provide type checking as states are modified in the future; helping ensure we handle all states

Below, we’ll take a look at a couple examples of how we’ve used this pattern.

Transient UI Messages

1-off messages such as Toasts and Snackbars can be nicely modeled this way, allowing a ViewModel to indicate that different message types can be shown without having to set up different UI bindings for each individual message.

Additionally, by using a pub/sub model for the states, they are more transient in nature than If we were using a data binding field to bind the messages.

We’ll start with Toasts since they are the simple case.

We’ve defined a ShortToast and LongToast class that can be published from the ViewModel to show different duration Toast messages.

 

ToastData

Definitions of the supported Toast states

 

Our BaseViewModel exposes a PublishSubject for the Toast states

 

Our Activities then subscribe to the ViewModel’s PublishSubject and respond to the different Toast states

 

Toast is okay, but let’s try something more exciting; Snacks

SnackData

SnackBars are a convenient way of showing a quick, transient message that also may have an associated action.

Handling the message and the possible action is handled nicely using this pattern by encapsulating the message content, and the action in the state class.

The handling BaseActivity then binds to the actual SnackBar and handles listening for the UI state changes of the actual view.

Definition of the supported SnackBar states

 

Our BaseViewModel exposes a PublishSubject for the SnackBar states

 

Our Activities then subscribe to the ViewModel’s PublishSubject and respond to the different Snackbar states

 

ViewModel Lifecycle

We also have found that representing state transitions to/from screens can be handled nicely with this Sealed Class pattern.

We’ve defined a number of ViewModel lifecycle states than are then handled using the same pub/sub model as before.

Our BaseActivity subscribes and handles the indicated state transitions, and because we used Sealed Classes we get nice IDE messages if we aren’t handling all the states.

Because Sealed Classes also can contain their own state, we can include things like results codes, request codes, or extras with our state transitions which helps give our ViewModels as much control of these transitions as possible.

We define a number of action states for our ViewModels

 

Those states can then be used to indicate that ViewModels should be finished with some result, cancelled, or a new screen should be started

 

Our BaseActivity then knows how to respond to each lifecycle state and makes the appropriate transitions

 

Challenges & Open Questions

  • the current naming is in its own way tied to the framework which goes against our idea of a nice clean mvvm
  • ideally would have the state classes be completely framework-independent to improve testability. We went down this path, and found we started to reach diminishing returns where the work required to avoid using Intent and Bundle classes was more than we wanted to support
  • this method requires us to have some type of “bind viewmodel” function in our BaseActivity that subscribes to the appropriate state subjects. It’s not a big hit, but is an extra boilerplate step beyond what is handled by DataBinding. We could possibly create a custom BindingAdapter that could handle this boilerplate for us, but haven’t tried it yet.

 

Further Reading On This Pattern

 

Conclusion

Kotlin’s Sealed Classes are a useful tool for modeling restricted states within your various app flows. Restrictive compilation rules and IDE checks help ensure new states are handled in the future, and their ability to hold their own state makes them useful for representing states that can hold different values such as transient UI messages.

We’ve found these examples to be useful, and hope you might find the same.

 

This article was posted originally on the Udacity Engineering Blog

 

Related Content

Learning Kotlin by Mistake

Adopting Kotlin

 

Subscribe

 


I love to meet/talk/discuss with others so if you have feedback or want to chat you can follow me on Twitter, on Medium, or subscribe here to stay up to date on my blog.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.