KotlinConf — Representing State: the Kotlin Edition by Christina Lee.

Ivana Tanova
4 min readJun 9, 2020

--

Hey guys, in this article I am going to summarise an excelent talk by Christina Lee about good and bad practices when representing application state. State should be state of the art :)

We all have the same problem — we need our code to be clear, easy for refactoring, SOLID. How can we achieve this while having so many different variables at the same time and supporting constant changes? What problems Christina saw in her company’s application?

Main take aways:

  • Boolean varialbes lack context, it is better to use sealed classes or interfaces.
  • Strings and numbers have infinite possible values — wrap them inside enums to make them finite.
  • When you are creating a data type (object) ensure that it cannot have misleading states — use var, val, nullable or not.
  • If something can go wrong it goes — unreachable code should be unreachable at compile time.

No one needs the true (without a context)

Problem: boolean variables can be interpreted in different ways. They are too lousy to describe wich problem they solve and usually end up as used improperly. Also they are hard to refactor.

We all use various booleans scattered all over the place. So what is wrong with this:

isHeaderAdded = true

if(isHeaderAdded)

// do stuffs with header

Seems harmless, but when you put another one:

isHeaderAdded = true

isFirstItemEmpty = false

if(isHeaderAdded && isFirstItemEmpty&&..)

Now at a first glance I bet you can’t tell the logic of the whole block.

The problems with booleans is that they miss the context, their values can be interpreted in different ways. You can use variables isYellow or isNotYellow and true will mean different things. They are hard for refactoring (which you all know is our main job)

## Solution 1

Put those variables inside domain. We can use Kotlin sealed class!

sealed class ContentState{

object HasHeader(): ContentState

object NoHeader(): ContentState

}

## Solution 2

Harness the expressive types abilities. Let’s use an interface instead of boolean.

interface HeaderList

interface HeaderlessList

Now we can use:

if(item is HeaderList)

items.filter(it is HeaderList)

Infinite is bad

Problem: Sometimes we need a string or integer to represent our state. But beware — their values can be misleading.

We know the rules: use constants, don’t rely on hardcoded types. But how can we be sure that a string representing url is representing our production url or develop one. You can expect values as 1, 2, 3…100; what does this mean? We need fixed set of input values to know that all inputs are handled properly.

// DON’T

val url = “some cool url”

## Solution 1

  • Don’t type a string twice.
  • Don’t use strings if possible.

Strings are for data, not for state

## Solution 2

Wrap infinite numbers inside enums:

// DO

enum Endpoints{

Production(url), Testing(url)

}

Now you can use it like:

Endpoints.Production.url

Beware of illegal data structures

Problem: Sometimes we create objects that can have illogical state.

Imagine that you have a door. Now representing that door as Door(isOpen, isClosed) doesn’t make sense, right? The door is either open or closed. But we often do this in our code.

When you create an object check carefully if there is a possibility for illogical state. Here is what to check:

  • Things that should not be missing are missing- for example you can have a body, but without a head.
  • Illogical state — door is open and closed at the same time.
  • Less or more things that you need — three legs. Attention— it is possible to miss a leg or two!

Use val for things that cannot be changed.

Use var for things that can be changed.

Pair<Leg?, Leg?> — could have none, one or two.

If Something Can Go Wrong — It Goes

Christina shares how their developer’s options somehow ended in the hands of the end users in production. The chance for this to happen was too little, but it happend.

## Solution: Hidden code should be unreachable at compile time.

Problem: We assume things about the code that are not always true.

var isThisPageRed = false // BAD CODE — don’t just assume things!

model!!.getArticle() // WRONG don’t rely that the model won’t be null

## Solution: Use object for state.

val objectState = ObjectState.Uninitialized

if(objectState !== ObjectState.Uninitialized)

Be carefull with lateinit. It can cause crashes. Someone can use your object before it is initialized! For example for logging purposes.

Thanlk you! Here is your prize for reading this post:

(yep — a puppy pic :)

--

--

Ivana Tanova

Android enthusiast and keen tea drinker. I love to learn and to be funny, both for me are passions.