---
title: "Tidy up your Observable Streams with Kotlin's Sealed Classes"
source: "/writing/tidy-up-your-observable-streams-with-kotlins-sealed-classes/"
---

Article

# Tidy up your Observable Streams with Kotlin's Sealed Classes

Collin Flynn

August 30, 2017

If you have ever read a sterile _how-to_ example of using RxJava in your network layer, you might find this familiar:

userService.updateEmail("newEmail@example.com")
        .subscribeOn(Schedulers._io_())
        .observeOn(AndroidSchedulers._mainThread_())
        .subscribe(new SingleObserver<UserInfo>() {
            //...
        })

Here we have some asynchronous call to update an email address, and an `Observer` standing ready to handle the result. As samples go, it's not trying to solve more problems than absolutely necessary while demonstrating the basics.

But just beyond the basics are the every-day **error conditions**:

-   Permission Denied
-   Sign In Required
-   Invalid Email

No problem.

You might think to handle your error states inside `onError(Throwable t)`, but there are [good reasons not to](http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/) . `onError()` should be reserved for when you're unlikely to gracefully recover. However, we _can_ gracefully handle errors like _permission rejection_ or _invalid inputs_. Rather than violently terminating the stream with an `Exception`, these error conditions should be pushed through `onSuccess().`

But how can we model the success case and all the expected error cases in one stream of `UserInfo`? **Kotlin has a beautiful solution**, but before looking at that, lets examine a Java approach and observe its problems.

### The Java Way

Suppose we combine both successes and expected errors into a composite object, such that you can inspect the payload for success or failure in a single model:

onSuccess(Either<UserInfo, EmailUpdateError> result)

Here an `Either` holds two `Optional<T>` fields and allows you check which is present.

In fact, Retrofit's [Response](https://github.com/square/retrofit/blob/e6a7cd01657670807bed24f6f4ed56eb59c9c9ab/retrofit/src/main/java/retrofit2/Response.java) (and [Result](https://github.com/square/retrofit/blob/e6a7cd01657670807bed24f6f4ed56eb59c9c9ab/retrofit-adapters/rxjava/src/main/java/retrofit2/adapter/rxjava/Result.java) ) wrappers are built with a similar idea. If you opt-in, Retrofit's RxJava Call Adapter will wrap most non-200s in `Result<T>` and send them through `onSuccess()`.

But Java makes handling compound `Nullable` types **very ugly**. `Response<T>` does a fine job guarding against nullability, but any composite object like `Response<T>` or `Either<Success, Failure>` relies on the consuming developer to manually access `Nullable` (or `Optional<T>`) fields:

if (either.isSuccess()) {
    UserInfo userInfo = either.success().get();
    // handle success
} else {
    EmailUpdateFailure failure = either.failure().get();
    // handle failure
}

This increases branching, depends on documentation contracts, and is verbose. Any model type of your own creation will have the same core problem.

And don't even think about using `.zipWith()` to coalesce multiples:

You could collapse the models down to a more elegant wrapper (other than Pair<>), but the problem remains: nullable fields and manual state checking.

Yuck.

### Use Kotlin's Sealed Classes

With a sealed class, you can define each case as its own type.

The compiler can know for sure that all EmailUpdate objects will be one of these types.

Notice they each may have unique properties. Unlike a typical class hierarchy, the possibilities are bounded and therefore the compiler can help you check your work.

Handling an `EmailUpdate` is easy. Use `[when](https://kotlinlang.org/docs/reference/control-flow.html#when-expression)` , an upgrade over Java's `switch`:

override fun onSuccess(result: EmailUpdate) {
    when (result) {
        is Success -> publish(result.updatedProfile)
        PermissionDenied -> onPermissionDenied()
        SignInToContinue -> showSignInDialog()
        is EmailInvalid ->
            showEmailError(result.input, result.error)
    }
}

Isn't that better? You can access the `EmailUpdate`'s properties in each case without nullability. Kotlin will not only smart-cast to the appropriate type, it can also ensure that your `when` statement is exhaustive (if you've prefixed with `return`). When another developer adds a case to `EmailUpdate`, the compiler will prevent them from neglecting that additional possibility.

Mmmm.

Sealed classes (and more broadly, [algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type#Programming_languages_with_algebraic_data_types) ) are not a new idea. `Elm`'s [Union Types](https://guide.elm-lang.org/types/union_types.html) and `F#`'s [Discriminated Unions](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions) are powerful versions of the same concept. Functional languages in particular have a lot to gain by supporting terse union-type syntax: functions return one value! [Swift's enums](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html) also allow for type-safe handling of non-homogenous cases.

Don't let compound return types clutter your stream semantics. Start using sealed classes in Kotlin!

_Collin writes code with_ [_Livefront_](http://livefront.com/) _and he thanks you for reading._

-   Collin Flynn

    Principal Software Engineer

    Collin Flynn

### Tags

[Digital Engineering](/insights/all/?topic=digital-engineering) [Mobile Apps](/insights/all/?topic=mobile-apps)

### Share

-   Copied

## View these next.

[See all](/insights/all/?type=writing)

-   [

    Article

    I Ditched User Personas -- And Here's Why You Should Too

    Ali Joaquin

    ](/writing/i-ditched-user-personas-and-heres-why-you-should-too/)
-   [

    Article

    The (best) way to perform online user testing with Principle

    Andy Davis

    ](/writing/the-best-way-to-perform-online-user-testing-with-principle/)
-   [

    Article

    Design Audits: Adding One More Hat to the Product Design Haberdashery

    Chris Wodicka

    ](/writing/design-audits-adding-one-more-hat-to-the-product-design-haberdashery/)
