Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/guide/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The update function uses these two concepts to take a previous state and transfo
So far we have mentioned two conventions: models are `data` classes, and messages are `sealed` classes. You can see in the function below how those modifiers are leveraged. The message type is able to be determined in an exhaustive manner using the `when` block. The new state is created by mutating the old state with the `copy` function.

```kotlin
val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg, model ->
val update: Update<Model, Msg> /* Update<Model, Msg> */ = { msg, model ->
when (msg) {
Msg.Increment -> model.copy(count = model.count + 1) to none()
Msg.Decrement -> model.copy(count = model.count - 1) to none()
Expand Down Expand Up @@ -65,7 +65,7 @@ The view function, as mentioned above, takes the current state its argument and
* `decrement` - a function which dispatches the `Decrement` message.

```kotlin
val view: (Model) -> Props = { model ->
val view: View<Model, Props> /* View<Model, Props> */ = { model ->
Props(
model.count,
{ dispatch -> dispatch(Msg.Increment) },
Expand All @@ -83,7 +83,7 @@ Now that we've built the core components of our application we need a few more t
To get the runtime loop started, we first need to know what the initial state is. We do this by definiting an initialization function. This function is similar to the `update` function, however it takes no arguments. By convention, it is often desireable to define defaults in the model class and simply return a new instance from the init function.

```kotlin
val init: () -> Pair<Model, Effect<Msg>> = {
val init: Init<Model, Msg> /* Init<Model, Msg> */ = {
Model() to none()
}
```
Expand All @@ -93,7 +93,7 @@ val init: () -> Pair<Model, Effect<Msg>> = {
We also need to know how to render the view properties returned by the view function. Each target platform does this by implementing a render function which takes the view properties and a dispatch function as arguments. The dispatch function can be invoked to send messages to the update function.

```kotlin
val render: (Props, Dispatch<Msg>) -> Any? = { props, dispatch ->
val render: Render<Msg, Props> /* Render<Msg, Props> */ = { props, dispatch ->
// Platform specific rendering
countLabel.text = "${props.count}"
incrementButton.setOnClickListener { dispatch(props.increment()) }
Expand All @@ -103,10 +103,10 @@ val render: (Props, Dispatch<Msg>) -> Any? = { props, dispatch ->

#### Runtime

The Oolong runtime composes these core functions into a user interaction loop, continually moving from one state to the next. It also handles things like side-effects (which we'll see in the next chapter) and resource disposal. You can start this loop by calling `Oolong.runtime`.
The Oolong runtime composes these core functions into a user interaction loop, continually movingo~~~~ from one state to the next. It also handles things like side-effects (which we'll see in the next chapter) and resource disposal. You can start this loop by calling `Oolong.runtime`.

```kotlin
val dispose = Oolong.runtime(
val dispose = runtime(
init,
update,
view,
Expand Down
22 changes: 11 additions & 11 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ class Props(
val decrement: (Dispatch<Msg>) -> Unit
)

val init: () -> Pair<Model, Effect<Msg>> = {
val init: Init<Model, Msg> = {
Model() to none()
}

val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg, model ->
val update: Update<Model, Msg> = { msg, model ->
when (msg) {
Msg.Increment -> model.copy(count = model.count + 1)
Msg.Decrement -> model.copy(count = model.count - 1)
} to none()
}

val view: (Model) -> Props = { model ->
val view: View<Model, Props> = { model ->
Props(
model.count,
{ dispatch -> dispatch(Msg.Increment) },
Expand All @@ -64,10 +64,10 @@ import oolong.Oolong
import oolong.effect.none

fun <Model : Any, Msg : Any, Props : Any> CoroutineScope.runtime(
init: () -> Pair<Model, Effect<Msg>>,
update: (Msg, Model) -> Pair<Model, Effect<Msg>>,
view: (Model) -> Props,
render: (Props, Dispatch<Msg>) -> Any?,
init: Init<Model, Msg>,
update: Update<Model, Msg>,
view: View<Model, Props>,
render: Render<Msg, Props>,
): Dispose = Oolong.runtime(
init,
update,
Expand All @@ -93,18 +93,18 @@ class Props(
val decrement: (Dispatch<Msg>) -> Unit
)

val init: () -> Pair<Model, Effect<Msg>> = {
val init: Init<Model, Msg> = {
Model() to none()
}

val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg, model ->
val update: Update<Model, Msg> = { msg, model ->
when (msg) {
Msg.Increment -> model.copy(count = model.count + 1)
Msg.Decrement -> model.copy(count = model.count - 1)
} to none()
}

val view: (Model) -> Props = { model ->
val view: View<Model, Props> = { model ->
Props(
model.count,
{ dispatch -> dispatch(Msg.Increment) },
Expand All @@ -115,7 +115,7 @@ val view: (Model) -> Props = { model ->
fun main() {
runBlocking {
//sampleStart
val render: (Props, Dispatch<Msg>) -> Any? = { props, dispatch ->
val render: Render<Msg, Props> = { props, dispatch ->
// Print the current count
println("count: ${props.count}")

Expand Down
12 changes: 6 additions & 6 deletions docs/recipes/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sealed class Msg {
data class List(msg: List.Msg) : Msg()
data class Detail(msg: Detail.Msg) : Msg()
// Navigation
data class SetScreen(next: Pair<Model, Effect<Msg>>): Msg()
data class SetScreen(next: Next<Model, Msg> /* Pair<Model, Effect<Msg>> */): Msg()
}
```

Expand All @@ -42,20 +42,20 @@ sealed class Props {

Now that the appropriate types have been defined, we can define program functions which delegate to each screen. Let's look each function in order starting with `init`.

Oolong provides a few utility functions for common use-cases and one of these is [`bimap`](/oolong/oolong.next/bimap). The bimap function transforms an instance of `Pair<A, Effect<B>>` to an instance of `Pair<C, Effect<D>>`. We're going to use `List` as our initial screen, so in this case we're bimapping from an instance of `Pair<List.Model , Effect<List.Msg >>` to an instance of `Pair<Model, Effect<Msg>>`.
Oolong provides a few utility functions for common use-cases and one of these is [`bimap`](/oolong/oolong.next/bimap). The bimap function transforms an instance of `Pair<A, Effect<B>>` to an instance of `Pair<C, Effect<D>>`. We're going to use `List` as our initial screen, so in this case we're bimapping from an instance of `Pair<List.Model , Effect<List.Msg>>` to an instance of `Next<Model, Msg>`.

In other words, we're taking the `List.Model` and `List.Msg` returned from `List.init` and wrapping them in the delegated types of `Model.List` and `Msg.List`.

```kotlin
val init: () -> Pair<Model, Effect<Msg>> = {
val init: Init<Model, Msg> = {
bimap(List.init(), Model::List, Msg::List)
}
```

The same `bimap` function is used to delegate to screens in the `update` function. If the `msg` is an instance of a screen message wrapper, then we delegate to that screen using `bimap`. However, we receive a `SetScreen` message, we simply return the next value provided.

```kotlin
val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg, model ->
val update: Update<Model, Msg> = { msg, model ->
when (msg) {
is Msg.List -> {
bimap(
Expand All @@ -81,7 +81,7 @@ val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg, model ->
The `view` function is quite simple, as we only need to wrap the screen's props with it's respected instance in the navigation props.

```kotlin
val view: (Model) -> Props = { model ->
val view: View<Model, Props> = { model ->
when (model) {
is Model.List -> {
Props.List(List.view(model.model))
Expand All @@ -96,7 +96,7 @@ val view: (Model) -> Props = { model ->
Finally, in the `view` function we unwrap the props and delegate to each screen's render function. There is one additional consideration we need to take in this function, however, which is mapping the `dispatch` function from the screen's `Msg` type to the parent's. For this, we can use the provided [`contramap`](/oolong/oolong.dispatch/contramap) fuction.

```kotlin
val render: (Props, Dispatch<Msg>) -> Any? = { props, dispatch ->
val render: Render<Msg, Props> = { props, dispatch ->
when (props) {
is Props.List -> {
List.render(props.props, contramap(dispatch, Msg::List))
Expand Down
24 changes: 12 additions & 12 deletions oolong/src/commonMain/kotlin/oolong/runtime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import kotlin.jvm.JvmOverloads
*/
@JvmOverloads
fun <Model, Msg> runtime(
init: () -> Pair<Model, Effect<Msg>>,
update: (Msg, Model) -> Pair<Model, Effect<Msg>>,
view: (Model, Dispatch<Msg>) -> Any?,
init: Init<Model, Msg>,
update: Update<Model, Msg>,
view: Render<Msg, Model>,
runtimeContext: CoroutineContext = Dispatchers.Default,
renderContext: CoroutineContext = Dispatchers.Main,
effectContext: CoroutineContext = Dispatchers.Default
Expand All @@ -27,10 +27,10 @@ fun <Model, Msg> runtime(
*/
@JvmOverloads
fun <Model, Msg, Props> runtime(
init: () -> Pair<Model, Effect<Msg>>,
update: (Msg, Model) -> Pair<Model, Effect<Msg>>,
view: (Model) -> Props,
render: (Props, Dispatch<Msg>) -> Any?,
init: Init<Model, Msg>,
update: Update<Model, Msg>,
view: View<Model, Props>,
render: Render<Msg, Props>,
runtimeContext: CoroutineContext = Dispatchers.Default,
renderContext: CoroutineContext = Dispatchers.Main,
effectContext: CoroutineContext = Dispatchers.Default
Expand All @@ -43,10 +43,10 @@ fun <Model, Msg, Props> runtime(
}

private class RuntimeImpl<Model, Msg, Props>(
init: () -> Pair<Model, Effect<Msg>>,
private val update: (Msg, Model) -> Pair<Model, Effect<Msg>>,
private val view: (Model) -> Props,
private val render: (Props, Dispatch<Msg>) -> Any?,
init: Init<Model, Msg>,
private val update: Update<Model, Msg>,
private val view: View<Model, Props>,
private val render: Render<Msg, Props>,
private val runtimeContext: CoroutineContext,
private val renderContext: CoroutineContext,
private val effectContext: CoroutineContext
Expand All @@ -71,7 +71,7 @@ private class RuntimeImpl<Model, Msg, Props>(
}
}

private fun step(next: Pair<Model, Effect<Msg>>) {
private fun step(next: Next<Model, Msg>) {
val (state, effect) = next
val props = view(state)
currentState = state
Expand Down
66 changes: 62 additions & 4 deletions oolong/src/commonMain/kotlin/oolong/types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,78 @@ fun <Msg> effect(block: Effect<Msg>): Effect<Msg> = block
/**
* Init builder function.
*/
fun <Model, Msg> init(block: () -> Pair<Model, Effect<Msg>>): () -> Pair<Model, Effect<Msg>> = block
fun <Model, Msg> init(block: Init<Model, Msg>): Init<Model, Msg> = block

/**
* Update builder function.
*/
fun <Model, Msg> update(block: (Msg, Model) -> Pair<Model, Effect<Msg>>): (Msg, Model) -> Pair<Model, Effect<Msg>> =
fun <Model, Msg> update(block: Update<Model, Msg>): Update<Model, Msg> =
block

/**
* View builder function.
*/
fun <Model, Props> view(block: (Model) -> Props): (Model) -> Props = block
fun <Model, Props> view(block: View<Model, Props>): View<Model, Props> = block

/**
* Render builder function.
*/
fun <Props, Msg> render(block: (Props, Dispatch<Msg>) -> Any?): (Props, Dispatch<Msg>) -> Any? = block
fun <Props, Msg> render(block: Render<Msg, Props>): Render<Msg, Props> = block

/*
Typealiases for the function signatures so that they're easily
referencable and a single source of truth for their signature
*/

/**
* A pair of the next state and side-effects
*/
@Deprecated(
"Use Pair<Model, Effect<Msg>> instead",
ReplaceWith("Pair<Model, Effect<Msg>>", "kotlin.Pair", "oolong.Effect")
)
typealias Next<Model, Msg> = Pair<Model, Effect<Msg>>

/**
* Creates an initial state and side-effects
*/
@Deprecated(
"Use () -> Pair<Model, Effect<Msg>> instead",
ReplaceWith("() -> Pair<Model, Effect<Msg>>", "kotlin.Pair", "oolong.Effect")
)
typealias Init<Model, Msg> = () -> Pair<Model, Effect<Msg>>

/**
* Creates a next state and side-effects from a message and current state
*
* @param msg the message to interpret
* @param model the current state
*/
@Deprecated(
"Use (Msg, Model) -> Pair<Model, Effect<Msg>> instead",
ReplaceWith("(Msg, Model) -> Pair<Model, Effect<Msg>>", "kotlin.Pair", "oolong.Effect")
)
typealias Update<Model, Msg> = (msg: Msg, model: Model) -> Pair<Model, Effect<Msg>>

/**
* Creates view properties from the current state
*
* @param model the current state
* @param dispatch the dispatch function
*/
@Deprecated(
"Use (Model) -> Props instead",
ReplaceWith("(Model) -> Props")
)
typealias View<Model, Props> = (model: Model) -> Props

/**
* Renders the view properties
*
* @param props view properties
*/
@Deprecated(
"Use (Props, Dispatch<Msg>) -> Any? instead",
ReplaceWith("(Props, Dispatch<Msg>) -> Any?", "oolong.Dispatch")
)
typealias Render<Msg, Props> = (props: Props, dispatch: Dispatch<Msg>) -> Any?
8 changes: 4 additions & 4 deletions oolong/src/jvmTest/kotlin/oolong/RuntimeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ class RuntimeTest {
}

private fun <Model, Msg, Props> TestCoroutineScope.runtime(
init: () -> Pair<Model, Effect<Msg>>,
update: (Msg, Model) -> Pair<Model, Effect<Msg>>,
view: (Model) -> Props,
render: (Props, Dispatch<Msg>) -> Any?
init: Init<Model, Msg>,
update: Update<Model, Msg>,
view: View<Model, Props>,
render: Render<Msg, Props>
): Job = runtime(
init,
update,
Expand Down
2 changes: 0 additions & 2 deletions oolong/src/jvmTest/kotlin/oolong/effect/EffectTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package oolong.effect
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.runTest
import oolong.Effect
import oolong.effect
Expand Down