Skip to content

Commit

Permalink
Updated samples
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed May 1, 2024
1 parent 9721595 commit 0713750
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 297 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.arkivanov.mvikotlin.extensions.coroutines.events
import com.arkivanov.mvikotlin.extensions.coroutines.labels
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.arkivanov.mvikotlin.sample.coroutines.shared.TodoDispatchers
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.Label
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.Label
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.detailsStore
import com.arkivanov.mvikotlin.sample.database.TodoDatabase
import com.arkivanov.mvikotlin.sample.database.TodoItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.arkivanov.mvikotlin.sample.coroutines.shared.details

import com.arkivanov.mvikotlin.sample.coroutines.shared.details.DetailsView.Event
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.DetailsView.Model
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.Intent
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.State
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.Intent
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.State

internal val stateToModel: (State) -> Model =
{ state ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,104 @@
package com.arkivanov.mvikotlin.sample.coroutines.shared.details.store

import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.core.utils.JvmSerializable
import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutorScope
import com.arkivanov.mvikotlin.extensions.coroutines.coroutineExecutorFactory
import com.arkivanov.mvikotlin.sample.database.TodoDatabase
import com.arkivanov.mvikotlin.sample.database.TodoItem
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.Intent
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.Label
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.State

internal interface DetailsStore : Store<Intent, State, Label> {

// Serializable only for exporting events in Time Travel, no need otherwise.
sealed class Intent : JvmSerializable {
data class SetText(val text: String) : Intent()
data object ToggleDone : Intent()
data object Delete : Intent()
}

data class State(
val data: TodoItem.Data? = null,
val isFinished: Boolean = false,
) : JvmSerializable // Serializable only for exporting events in Time Travel, no need otherwise.

// Serializable only for exporting events in Time Travel, no need otherwise.
sealed class Label : JvmSerializable {
data class Changed(val id: String, val data: TodoItem.Data) : Label()
data class Deleted(val id: String) : Label()
}
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

// Serializable only for exporting events in Time Travel, no need otherwise.
internal sealed class Intent : JvmSerializable {
data class SetText(val text: String) : Intent()
data object ToggleDone : Intent()
data object Delete : Intent()
}

internal data class State(
val data: TodoItem.Data? = null,
val isFinished: Boolean = false,
) : JvmSerializable // Serializable only for exporting events in Time Travel, no need otherwise.

// Serializable only for exporting events in Time Travel, no need otherwise.
internal sealed class Label : JvmSerializable {
data class Changed(val id: String, val data: TodoItem.Data) : Label()
data class Deleted(val id: String) : Label()
}

// Serializable only for exporting events in Time Travel, no need otherwise.
private sealed class Msg : JvmSerializable {
data class Loaded(val data: TodoItem.Data) : Msg()
data object Finished : Msg()
data class TextChanged(val text: String) : Msg()
data object DoneToggled : Msg()
}

private typealias ExecutorScope = CoroutineExecutorScope<State, Msg, Unit, Label>

/**
* This builder function showcases the way of creating a [Store] using
* the DSL API and *without* a dedicated interface.
* This option may work better for smaller and simpler stores.
* The [Intent], [State] and [Label] classes are top-level, the returned
* type is just the generic [Store] interface.
*/
internal fun StoreFactory.detailsStore(
database: TodoDatabase,
mainContext: CoroutineContext,
ioContext: CoroutineContext,
itemId: String,
): Store<Intent, State, Label> =
create<Intent, Unit, Msg, State, Label>(
name = "TodoDetailsStore",
initialState = State(),
bootstrapper = SimpleBootstrapper(Unit),
executorFactory = coroutineExecutorFactory(mainContext) {
fun ExecutorScope.save() {
val data = state().data ?: return
publish(Label.Changed(itemId, data))

launch(ioContext) {
database.save(itemId, data)
}
}

onAction<Unit> {
launch {
val item: TodoItem? = withContext(ioContext) { database.get(itemId) }
dispatch(item?.data?.let(Msg::Loaded) ?: Msg.Finished)
}
}

onIntent<Intent.SetText> {
dispatch(Msg.TextChanged(it.text))
save()
}

onIntent<Intent.ToggleDone> {
dispatch(Msg.DoneToggled)
save()
}

onIntent<Intent.Delete> {
publish(Label.Deleted(itemId))

launch {
withContext(ioContext) { database.delete(itemId) }
dispatch(Msg.Finished)
}
}
},
reducer = { msg: Msg ->
when (msg) {
is Msg.Loaded -> copy(data = msg.data)
is Msg.Finished -> copy(isFinished = true)
is Msg.TextChanged -> copy(data = data?.copy(text = msg.text))
is Msg.DoneToggled -> copy(data = data?.copy(isDone = !data.isDone))
}
},
)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.arkivanov.mvikotlin.sample.coroutines.shared.main.store.add
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.core.utils.JvmSerializable
import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
import com.arkivanov.mvikotlin.extensions.coroutines.coroutineExecutorFactory
import com.arkivanov.mvikotlin.sample.coroutines.shared.main.store.add.AddStore.Intent
import com.arkivanov.mvikotlin.sample.coroutines.shared.main.store.add.AddStore.Label
import com.arkivanov.mvikotlin.sample.coroutines.shared.main.store.add.AddStore.State
Expand All @@ -13,6 +13,12 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

/**
* This builder function showcases the way of creating a [Store] *without*
* using the DSL API and *with* the dedicated interface [AddStore].
* This option may work better for smaller and simpler stores.
* The [Intent], [State] and [Label] classes are defined inside the store interface.
*/
internal fun StoreFactory.addStore(
database: TodoDatabase,
mainContext: CoroutineContext,
Expand All @@ -21,46 +27,28 @@ internal fun StoreFactory.addStore(
object : AddStore, Store<Intent, State, Label> by create(
name = "TodoAddStore",
initialState = State(),
executorFactory = {
ExecutorImpl(
database = database,
mainContext = mainContext,
ioContext = ioContext,
)
executorFactory = coroutineExecutorFactory(mainContext) {
onIntent<Intent.SetText> { dispatch(Msg.TextChanged(it.text)) }

onIntent<Intent.Add> {
val text = state().text.takeUnless(String::isBlank) ?: return@onIntent

dispatch(Msg.TextChanged(""))

launch {
val item = withContext(ioContext) { database.create(TodoItem.Data(text = text)) }
publish(Label.Added(item))
}
}
},
reducer = { msg: Msg ->
when (msg) {
is Msg.TextChanged -> copy(text = msg.text)
}
},
reducer = { reduce(it) },
) {}

// Serializable only for exporting events in Time Travel, no need otherwise.
private sealed class Msg : JvmSerializable {
data class TextChanged(val text: String) : Msg()
}

private class ExecutorImpl(
private val database: TodoDatabase,
mainContext: CoroutineContext,
private val ioContext: CoroutineContext,
) : CoroutineExecutor<Intent, Nothing, State, Msg, Label>(mainContext) {
override fun executeIntent(intent: Intent) {
when (intent) {
is Intent.SetText -> dispatch(Msg.TextChanged(intent.text))
is Intent.Add -> addItem()
}.let {}
}

private fun addItem() {
val text = state().text.takeUnless(String::isBlank) ?: return

dispatch(Msg.TextChanged(""))

scope.launch {
val item = withContext(ioContext) { database.create(TodoItem.Data(text = text)) }
publish(Label.Added(item))
}
}
}

private fun State.reduce(msg: Msg): State =
when (msg) {
is Msg.TextChanged -> copy(text = msg.text)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

/**
* This builder function showcases the way of creating a [Store] *without*
* using the DSL API and *with* the dedicated interface [AddStore].
* This option may work better for bigger and more complex stores.
* The [Intent], [State] and [Label] classes are defined inside the store interface.
*/
internal fun StoreFactory.listStore(
database: TodoDatabase,
mainContext: CoroutineContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.arkivanov.mvikotlin.sample.coroutines.shared.details.store

import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.utils.isAssertOnMainThreadEnabled
import com.arkivanov.mvikotlin.extensions.coroutines.labels
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.Intent
import com.arkivanov.mvikotlin.sample.coroutines.shared.details.store.DetailsStore.Label
import com.arkivanov.mvikotlin.sample.coroutines.shared.test
import com.arkivanov.mvikotlin.sample.database.MemoryTodoDatabase
import com.arkivanov.mvikotlin.sample.database.TodoItem
Expand All @@ -22,7 +21,7 @@ class DetailsStoreTest {
private val itemData = TodoItem.Data(text = "text", isDone = false)
private val database = MemoryTodoDatabase()

private lateinit var store: DetailsStore
private lateinit var store: Store<Intent, State, Label>

@BeforeTest
fun before() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.arkivanov.mvikotlin.extensions.reaktive.labels
import com.arkivanov.mvikotlin.extensions.reaktive.states
import com.arkivanov.mvikotlin.sample.database.TodoDatabase
import com.arkivanov.mvikotlin.sample.database.TodoItem
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.DetailsStore.Label
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.Label
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.detailsStore
import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.mapNotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.arkivanov.mvikotlin.sample.reaktive.shared.details

import com.arkivanov.mvikotlin.sample.reaktive.shared.details.DetailsView.Event
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.DetailsView.Model
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.DetailsStore.Intent
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.DetailsStore.State
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.Intent
import com.arkivanov.mvikotlin.sample.reaktive.shared.details.store.State

internal val stateToModel: (State) -> Model =
{ state ->
Expand Down
Loading

0 comments on commit 0713750

Please sign in to comment.