Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change messaging #211

Merged
merged 3 commits into from Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 95 additions & 0 deletions docs/faq.md
Expand Up @@ -15,3 +15,98 @@ Caused by: java.lang.IllegalStateException: Transition already happened. This is

### Callback is already defined.
TODO..

### After evaluation finished
If a `callback` or `eventCallback` is called after the Formula evaluation is finished, you will see
this exception.
```
Caused by: java.lang.IllegalStateException: Cannot call this after evaluation finished.
```
This can happen for a number of reasons. Likely, you are creating a `callback` or `eventCallback`
within the `onEvent` or `events` method of your `updates` lambda for the given Formula. This can
cause your callbacks to be scoped to a stale state instance. Instead, you should create your callbacks
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 evaluate(
input: Input,
state: State,
context: FormulaContext<State>
): Evaluation<TaskDetailRenderModel?> {
return Evaluation(
output = state.task,
updates = context.updates {
RxStream.fromObservable { repo.fetchTask(input.taskId) }.onEvent { task ->
val renderModel = TaskDetailRenderModel(
title = task.title,
// Don't do: calling context.callback within "onEvent" will cause a crash described above
onDeleteSelected = context.callback {
...
}
)
transition(state.copy(task = renderModel))
}
}
)
}
}
```
which the render model and then stores it in the `State`, we would store the fetched task from the RxStream 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 evaluate(
input: Input,
state: State,
context: FormulaContext<State>
): Evaluation<TaskDetailRenderModel?> {
// Note that this is correct because the render model and therefore callback is constructed
// within `evaluate` instead of within `onEvent`
val renderModel = state.task?.let {
TaskDetailRenderModel(
title = it.title,
onDeleteSelected = context.callback {
...
}
)
}
return Evaluation(
output = renderModel,
updates = context.updates {
RxStream.fromObservable { repo.fetchTask(input.taskId) }.onEvent { task ->
transition(state.copy(task = renderModel))
}
}
)
}
}
```
Notice that the render model is no longer stored in the state, but instead constructed on each
call to `evaluate` so that the callbacks are never stale.
Expand Up @@ -42,7 +42,7 @@ class FormulaContextImpl<State> internal constructor(

private fun ensureNotRunning() {
if (transitionCallback.running) {
throw IllegalStateException("cannot call this after evaluation finished.")
throw IllegalStateException("Cannot call this transition after evaluation finished. See https://instacart.github.io/formula/faq/#after-evaluation-finished")
}
}
}
Expand Up @@ -101,7 +101,7 @@ internal class ScopedCallbacks private constructor(

private fun ensureNotRunning() {
if (!enabled) {
throw java.lang.IllegalStateException("cannot call this after evaluation finished.")
throw java.lang.IllegalStateException("Cannot call this callback after evaluation finished. See https://instacart.github.io/formula/faq/#after-evaluation-finished")
}
}
}