---
title: How to Build and Test Decision Tree UX
teaser: In which I make up a concept, define it and solve for it.
tags: android,mobile,kotlin
author: Amanda Hill
published_on: 2017-03-22
---

If you've never heard of the term "Decision Tree UX", don't worry, you're not
alone. In fact, no one has ever heard of it, because I made it up! While
the term is new, the concept is not, and something most of you might already
be familiar with.

Consider a typical enrollment flow as an example. A user begins the flow at a
screen with several options for either signing up or logging in. While each
choice might lead the user down a different path, all the paths share the same
broader goal of authentication. And that folks, is Decision Tree UX. Or put in
the form of a formal definition...

> Decision Tree UX refers to the flow through a series of decisions and their
possible consequences to achieve a single, broader goal.

## Isn't every part of an app a leaf on a decision tree? 🍁🌲

Not exactly. The key to this concept is the "achieving a single
goal" part. For example, if you are just navigating around an app, you don't
really have a single goal. Another way of thinking about the "single goal"
might be to think of it as "single state" - where all your choices and all the
visuals you see are all related to a single state. To better understand this
concept, let's look at this, very scientific, decision tree:

![](https://images.thoughtbot.com/blog-vellum-image-uploads/eaaZgSeMTGi3SLPJOd8v_enhanced-buzz-20403-1361368042-3.jpg)

But since were talking about apps here, let's convert this tree into a
prototype.

🔮  _rubs magic ball_ 🔮

![](https://images.thoughtbot.com/blog-vellum-image-uploads/zKnSeTLpRCuPz9Bj83CT_prototype.png)

Tada 🎉 We have "Decision Tree UX"! While there are several different
screens, they are all related to the same "goal" or "state" of a cat's single
decision.

## Why Is It Hard to Code This? 🤔

I don't have a great answer to this question, other than to say, when I was
recently presented with the challenge of having to code a Decision Tree
UX, I was overwhelmed with the options for how to build it and sometimes having
too many options makes things hard.

## Some of the things I considered and why I didn't choose them 🙅

**Option:** Passing around the single model object from `Activity` to
`Activity` via `Intent`s.

**Why I Said "No Way Jose":** For my app the model object was fairly large and
passing it via an `Intent` wasn't really an option. Also, passing around an
object doesn't allow for a higher level understanding of what is happening.

**Option:** Inserting some data store into each `Activity` so they might access
the same object and pass the particular models' `id` via `Intent`s.

**Why I said "No Way Jose":** This was my runner up. What I didn't totally love
about this option was how many switch statements I was going to have to put
into each `Activity`. Depending on where the user was coming from, there were
several options for where to send them next, and I was going to have to put a
lot of, what I will call, "higher level logic" about the broader flow in each
individual `Activity`.

## Ingredients for a solution 📋

With my list of "what I don't like about other options" in hand, I set forth to
find a solution with the following features:

- Testable! Decisions trees are complicated, so testing was super important
- Types! I knew the more types I had the more explicit I could be in my testing
- All traversal logic (where to go when a button is tapped) in a single place,
  to make testing simple and clear

As I thought about these requirements and how to solve for them, I realized I
was really describing a parent / child relationship  - where the parent held
the state and acted like a puppeteer moving its children around. That thinking
led me to choose `Fragment`s as my means for implementation, because the
relationship between `Activity`s and `Fragment`s can mimic that of a parent /
child.

## Da Code 💻

Now that we understand the requirements and the things were trying to avoid,
let's start coding.

Using the above cat decision app as an example, let's begin with the
`Fragment`s. The above app has four distinct screens, so let's make four
`Fragment`s.

1. `StartFragment`
2. `QuestionFragment`
3. `AnswerAFragment`
4. `AnswerBFragment`

Now we know the parent `Activity`, let's call it `DecisionActivity`, will
initially begin by attaching `StartFragment` but what happens when a user taps
the start button, and how can we test that?

The answer? Interfaces! Let's give `StartFragment` an interface, called
`StartDelegate`, to call whenever the start button is clicked.

```kotlin
interface StartDelegate {
  fun onStartClick()
}
```

```kotlin
class StartFragment : Fragment() {

  lateinit var delegate: StartDelegate

  override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val startButton = view?.findViewById(R.id.start_button)
    startButton?.setOnClickListener { delegate.onStartClick() }
  }
}
```

But who will listen for that callback? The `DecisionActivity` is not a great
candidate since unit testing with `Activity`s is not always easy, so let's
make a class to manage all the coordination logic that we can more easily mock
and test. Say hello 👋to` DecisionCoordinator`.

```kotlin
class DecisionCoordinator() {

  fun startFlow() {
    val fragment = StartFragment.newInstance()
    fragment.delegate = ??
  }
}
```

Before we implement `StartDelegate` let's consider our options for who should
be the one conforming to it. One option would be to have `DecisionCoordinator`
conform. But that could quickly get messy since `onStartClick` isn't terribly
specific and theoretically other `Fragment`s could also have a `StartDelegate`,
which puts us back in a situation of having lots of switch statements in our
code. Another option, and the option I chose, is to use inner classes. Remember
earlier when I said I liked types? Well here they are folks! By having some
inner class confom to `StartDelegate` we have a single class whose sole
responsibility is handling the feedback from the `StartFragment`. Single
Responsibility FTW 💪

Now let's see the updated `DecisionCoordinator` with that inner class:

```kotlin
class DecisionCoordinator() {

  fun startFlow() {
    val fragment = StartFragment.newInstance()
    fragment.delegate = StartCoordinator()
  }

  inner class StartCoordinator : StartDelegate {

    override fun onStartClick() {
      //handle where to go when start is clicked
    }
  }
}
```

You might notice that we are not actually showing any `Fragment`s yet, and
there are two reasons for that. First, we are not "in" an `Activity` we don't
have access to a `FragmentManager` which can show a `Fragment`. Second, we want
to be able to test everything remember? So we want a solution that will allow
us to test that the correct `Fragment`s are being shown at the right times. Say
hello 👋 to `FragmentRouter`.

```kotlin
interface FragmentRouter {
  fun showFragment(fragment: Fragment)
}
```

Here's what happens when we inject `FragmentRouter` into the
`DecisionCoordinator`.

```diff
+ class DecisionCoordinator(val fragmentRouter: FragmentRouter) {

  fun startFlow() {
    val fragment = StartFragment.newInstance()
    fragment.delegate = StartCoordinator()
+   fragmentRouter.showFragment(fragment)
  }

  inner class StartCoordinator : StartDelegate {

    override fun onStartClick() {
      //handle where to go when start is clicked
    }
  }
```

Now when we test our `DecisionCoordinator` we can mock the `fragmentRouter` to
assert we are showing the correct `Fragment`s💃 But before we move onto the
tests, let's fill out the rest of the `DecisionCoordinator` with the remaining
logic:

```kotlin
class DecisionCoordinator(val fragmentRouter: FragmentRouter) {

  fun startDecision() {
    val fragment = StartFragment.newInstance()
    fragment.delegate = StartCoordinator()
    fragmentRouter.showFragment(fragment)
  }

  inner class StartCoordinator : StartDelegate {

    override fun onStartClick() {
      val fragment = QuestionFragment.newInstance()
      fragment.delegate = QuestionCoordinator()
      fragmentRouter.showFragment(fragment)
    }
  }

  inner class QuestionCoordinator : QuestionDelegate {

    override fun onYesClick() {
      val fragment = AnswerA.newInstance()
      fragmentRouter.showFragment(fragment)
    }

    override fun onNoClick() {
      val fragment = AnswerB.newInstance()
      fragmentRouter.showFragment(fragment)
    }
  }
}
```

And finally, let's take a look at those tests 👀

```kotlin
class DecisionCoordinatorTest {

  val fragmentRouter = mock<FragmentRouter>()

  @Test
  fun testStartCoordinator_onStartClick() {
    val decisionCoordinator = DecisionCoordinator(fragmentRouter)

    decisionCoordinator.StartCoordinator().onStartClick()

    verify(fragmentRouter).showFragment(isA<QuestionFragment>())
    verify(fragmentRouter).showFragment(check {
      assertTrue { (it as QuestionFragment).delegate is QuestionCoordinator }
    })
  }
}
```

A sight for sore eyes, isn't it? Our test is readable and clear and the
`DecisionCoordinator` fulfills all of our previous requirements. And that is a
solid day's work folks! Till next time 👋
