FAQ
Only thread that created it can trigger transitions
The state management should be initialized on the main thread and all the transitions should also happen on the main thread. You will get the following exception if that is not the case.
Caused by: java.lang.IllegalStateException: Only thread that created it can trigger transitions. Expected: main, Was: Network 1
Transition already happened.
After each transition, formula is re-evaluated and new event listeners are created. If you use an old listener you will see the following exception.
Caused by: java.lang.IllegalStateException: Transition already happened. This is using old event listener: $it.
Listener is already defined.
TODO..
After evaluation finished
If a onEvent is called after the Formula evaluation is finished, you will see this exception.
Caused by: java.lang.IllegalStateException: Cannot call this after evaluation finished.
onEvent after Formula.evalute already returned Evaluation
object. This can happen when you are calling onEvent within transition block. This is not 
allowed because your listener would be scoped to a stale state instance. Instead, you should 
create your listeners within the evaluate function itself, passing the data you might be 
using from the onEvent into the State defined for that Formula. For example, instead of:
class TaskDetailFormula @Inject constructor(
    private val repo: TasksRepo,
) : Formula<TaskDetailFormula.Input, TaskDetailFormula.State, TaskDetailRenderModel> {
    data class Input(
        val taskId: String
    )
    data class State(
        val task: TaskDetailRenderModel? = null
    )
    override fun initialState(input: Input) = State()
    override fun Snapshot<Input, State>.evaluate(): Evaluation<TaskDetailRenderModel?> {
        return Evaluation(
            output = state.task,
            actions = context.actions {
                RxAction.fromObservable { repo.fetchTask(input.taskId) }.onEvent { task ->
                  val renderModel = TaskDetailRenderModel(
                      title = task.title,
                      // Don't do: calling context.onEvent within "onEvent" will cause a crash described above
                      onDeleteSelected = context.onEvent {
                        ...
                      }
                   )
                   transition(state.copy(task = renderModel))
                }
            }
        )
    }
}
State, we would store the fetched task from the RxAction in
the state and then construct the render model in the evaluation function itself:
class TaskDetailFormula @Inject constructor(
    private val repo: TasksRepo,
) : Formula<TaskDetailFormula.Input, TaskDetailFormula.State, TaskDetailRenderModel> {
    data class Input(
        val taskId: String
    )
    data class State(
        val task: Task? = null
    )
    override fun initialState(input: Input) = State()
    override fun Snapshot<Input, State>.evaluate(): Evaluation<TaskDetailRenderModel?> {
        // Note that this is correct because the render model and therefore listener is constructed
        // within `evaluate` instead of within `onEvent`
        val renderModel = state.task?.let {
            TaskDetailRenderModel(
              title = it.title,
              onDeleteSelected = context.onEvent {
                ...
              }
            )
        }
        return Evaluation(
            output = renderModel,
            actions = context.actions {
                RxAction.fromObservable { repo.fetchTask(input.taskId) }.onEvent { task ->
                   transition(state.copy(task = renderModel))
                }
            }
        )
    }
}
evaluate so that the listeners are never stale.