Browse Source

Merge 'list' and 'add' modules into 'main'

pull/10/head
Arkadii Ivanov 4 years ago
parent
commit
d82762c18f
  1. 1
      examples/todoapp/common/add/.gitignore
  2. 19
      examples/todoapp/common/add/build.gradle.kts
  3. 2
      examples/todoapp/common/add/src/androidMain/AndroidManifest.xml
  4. 20
      examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/TodoAdd.kt
  5. 64
      examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/integration/TodoAddImpl.kt
  6. 23
      examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/integration/TodoAddStoreDatabase.kt
  7. 17
      examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/store/TodoAddStore.kt
  8. 51
      examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/store/TodoAddStoreProvider.kt
  9. 1
      examples/todoapp/common/list/.gitignore
  10. 27
      examples/todoapp/common/list/build.gradle.kts
  11. 2
      examples/todoapp/common/list/src/androidMain/AndroidManifest.xml
  12. 26
      examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/TodoList.kt
  13. 75
      examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListImpl.kt
  14. 16
      examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/store/TodoListStore.kt
  15. 12
      examples/todoapp/common/main/build.gradle.kts
  16. 11
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/ListOutputToOutput.kt
  17. 123
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainImpl.kt
  18. 23
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainStoreDatabase.kt
  19. 2
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoItem.kt
  20. 19
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStore.kt
  21. 33
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStoreProvider.kt
  22. 43
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/integration/TodoMainTest.kt
  23. 13
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TestTodoMainStoreDatabase.kt
  24. 42
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TodoMainStoreTest.kt
  25. 2
      examples/todoapp/settings.gradle.kts

1
examples/todoapp/common/add/.gitignore vendored

@ -1 +0,0 @@
/build

19
examples/todoapp/common/add/build.gradle.kts

@ -1,19 +0,0 @@
plugins {
id("multiplatform-setup")
id("multiplatform-compose-setup")
}
kotlin {
sourceSets {
named("commonMain") {
dependencies {
implementation(project(":common:utils"))
implementation(project(":common:database"))
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive)
implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.Badoo.Reaktive.reaktive)
}
}
}
}

2
examples/todoapp/common/add/src/androidMain/AndroidManifest.xml

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="example.todo.common.add"/>

20
examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/TodoAdd.kt

@ -1,20 +0,0 @@
package example.todo.common.add
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.core.store.StoreFactory
import example.todo.common.add.TodoAdd.Dependencies
import example.todo.common.add.integration.TodoAddImpl
import example.todo.common.utils.Component
import example.todo.database.TodoDatabase
interface TodoAdd : Component {
interface Dependencies {
val storeFactory: StoreFactory
val database: TodoDatabase
}
}
@Suppress("FunctionName") // Factory function
fun TodoAdd(componentContext: ComponentContext, dependencies: Dependencies): TodoAdd =
TodoAddImpl(componentContext, dependencies)

64
examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/integration/TodoAddImpl.kt

@ -1,64 +0,0 @@
package example.todo.common.add.integration
import androidx.compose.foundation.Text
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.ExperimentalKeyInput
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.keyInputFilter
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.ComponentContext
import example.todo.common.add.TodoAdd
import example.todo.common.add.TodoAdd.Dependencies
import example.todo.common.add.store.TodoAddStore.Intent
import example.todo.common.add.store.TodoAddStoreProvider
import example.todo.common.utils.composeState
import example.todo.common.utils.getStore
import example.todo.common.utils.onKeyUp
internal class TodoAddImpl(
componentContext: ComponentContext,
dependencies: Dependencies
) : TodoAdd, ComponentContext by componentContext, Dependencies by dependencies {
private val store =
instanceKeeper.getStore {
TodoAddStoreProvider(
storeFactory = storeFactory,
database = TodoAddStoreDatabase(queries = database.todoDatabaseQueries)
).provide()
}
@OptIn(ExperimentalKeyInput::class)
@Composable
override fun invoke() {
val state by store.composeState
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp)) {
OutlinedTextField(
value = state.text,
modifier = Modifier.weight(weight = 1F).keyInputFilter(onKeyUp(Key.Enter, ::onAddClicked)),
onValueChange = ::onTextChanged,
label = { Text(text = "Add a todo") }
)
Button(modifier = Modifier.padding(start = 8.dp), onClick = ::onAddClicked) {
Text(text = "+")
}
}
}
private fun onTextChanged(text: String) {
store.accept(Intent.SetText(text = text))
}
private fun onAddClicked() {
store.accept(Intent.Add)
}
}

23
examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/integration/TodoAddStoreDatabase.kt

@ -1,23 +0,0 @@
package example.todo.common.add.integration
import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.completable.completableFromFunction
import com.badoo.reaktive.completable.subscribeOn
import com.badoo.reaktive.scheduler.ioScheduler
import example.todo.common.add.store.TodoAddStoreProvider.Database
import example.todo.common.database.TodoDatabaseQueries
internal class TodoAddStoreDatabase(
private val queries: TodoDatabaseQueries
) : Database {
override fun add(text: String): Completable =
completableFromFunction {
queries.transactionWithResult {
queries.add(text = text)
val lastId = queries.getLastInsertId().executeAsOne()
queries.select(id = lastId).executeAsOne()
}
}
.subscribeOn(ioScheduler)
}

17
examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/store/TodoAddStore.kt

@ -1,17 +0,0 @@
package example.todo.common.add.store
import com.arkivanov.mvikotlin.core.store.Store
import example.todo.common.add.store.TodoAddStore.Intent
import example.todo.common.add.store.TodoAddStore.State
internal interface TodoAddStore : Store<Intent, State, Nothing> {
sealed class Intent {
data class SetText(val text: String) : Intent()
object Add : Intent()
}
data class State(
val text: String = ""
)
}

51
examples/todoapp/common/add/src/commonMain/kotlin/example/todo/common/add/store/TodoAddStoreProvider.kt

@ -1,51 +0,0 @@
package example.todo.common.add.store
import com.arkivanov.mvikotlin.core.store.Reducer
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.reaktive.ReaktiveExecutor
import com.badoo.reaktive.completable.Completable
import example.todo.common.add.store.TodoAddStore.Intent
import example.todo.common.add.store.TodoAddStore.State
internal class TodoAddStoreProvider(
private val storeFactory: StoreFactory,
private val database: Database
) {
fun provide(): TodoAddStore =
object : TodoAddStore, Store<Intent, State, Nothing> by storeFactory.create(
name = "TodoAddStore",
initialState = State(),
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {}
private sealed class Result {
data class TextChanged(val text: String) : Result()
}
private inner class ExecutorImpl : ReaktiveExecutor<Intent, Nothing, State, Result, Nothing>() {
override fun executeIntent(intent: Intent, getState: () -> State): Unit =
when (intent) {
is Intent.SetText -> dispatch(Result.TextChanged(text = intent.text))
is Intent.Add -> add(state = getState())
}
private fun add(state: State) {
dispatch(Result.TextChanged(text = ""))
database.add(text = state.text).subscribeScoped()
}
}
private object ReducerImpl : Reducer<State, Result> {
override fun State.reduce(result: Result): State =
when (result) {
is Result.TextChanged -> copy(text = result.text)
}
}
interface Database {
fun add(text: String): Completable
}
}

1
examples/todoapp/common/list/.gitignore vendored

@ -1 +0,0 @@
/build

27
examples/todoapp/common/list/build.gradle.kts

@ -1,27 +0,0 @@
plugins {
id("multiplatform-setup")
id("multiplatform-compose-setup")
}
kotlin {
sourceSets {
named("commonMain") {
dependencies {
implementation(project(":common:utils"))
implementation(project(":common:database"))
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive)
implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.Badoo.Reaktive.reaktive)
}
}
named("commonTest") {
dependencies {
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain)
implementation(Deps.Badoo.Reaktive.reaktiveTesting)
implementation(Deps.Badoo.Reaktive.utils)
}
}
}
}

2
examples/todoapp/common/list/src/androidMain/AndroidManifest.xml

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="example.todo.common.list"/>

26
examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/TodoList.kt

@ -1,26 +0,0 @@
package example.todo.common.list
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.badoo.reaktive.base.Consumer
import example.todo.common.list.TodoList.Dependencies
import example.todo.common.list.integration.TodoListImpl
import example.todo.common.utils.Component
import example.todo.database.TodoDatabase
interface TodoList : Component {
interface Dependencies {
val storeFactory: StoreFactory
val database: TodoDatabase
val listOutput: Consumer<Output>
}
sealed class Output {
data class ItemSelected(val id: Long) : Output()
}
}
@Suppress("FunctionName") // Factory function
fun TodoList(componentContext: ComponentContext, dependencies: Dependencies): TodoList =
TodoListImpl(componentContext, dependencies)

75
examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListImpl.kt

@ -1,75 +0,0 @@
package example.todo.common.list.integration
import androidx.compose.foundation.Text
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.material.Checkbox
import androidx.compose.material.Divider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.ComponentContext
import example.todo.common.list.TodoList
import example.todo.common.list.TodoList.Dependencies
import example.todo.common.list.TodoList.Output
import example.todo.common.list.store.TodoListStore
import example.todo.common.list.store.TodoListStore.Intent
import example.todo.common.list.store.TodoListStoreProvider
import example.todo.common.utils.composeState
import example.todo.common.utils.getStore
internal class TodoListImpl(
componentContext: ComponentContext,
dependencies: Dependencies
) : TodoList, ComponentContext by componentContext, Dependencies by dependencies {
private val store =
instanceKeeper.getStore {
TodoListStoreProvider(
storeFactory = storeFactory,
database = TodoListStoreDatabase(queries = database.todoDatabaseQueries)
).provide()
}
internal val state: TodoListStore.State get() = store.state
@Composable
override fun invoke() {
val state by store.composeState
LazyColumnFor(items = state.items) { item ->
Row(modifier = Modifier.clickable(onClick = { onItemClicked(id = item.id) }).padding(8.dp)) {
Text(
text = AnnotatedString(item.text),
modifier = Modifier.weight(1F),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(8.dp))
Checkbox(
checked = item.isDone,
onCheckedChange = { onDoneChanged(id = item.id, isDone = it) }
)
}
Divider()
}
}
internal fun onItemClicked(id: Long) {
listOutput.onNext(Output.ItemSelected(id = id))
}
internal fun onDoneChanged(id: Long, isDone: Boolean) {
store.accept(Intent.SetDone(id = id, isDone = isDone))
}
}

16
examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/store/TodoListStore.kt

@ -1,16 +0,0 @@
package example.todo.common.list.store
import com.arkivanov.mvikotlin.core.store.Store
import example.todo.common.list.store.TodoListStore.Intent
import example.todo.common.list.store.TodoListStore.State
internal interface TodoListStore : Store<Intent, State, Nothing> {
sealed class Intent {
data class SetDone(val id: Long, val isDone: Boolean) : Intent()
}
data class State(
val items: List<TodoItem> = emptyList()
)
}

12
examples/todoapp/common/main/build.gradle.kts

@ -9,11 +9,19 @@ kotlin {
dependencies {
implementation(project(":common:utils"))
implementation(project(":common:database"))
implementation(project(":common:list"))
implementation(project(":common:add"))
implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive)
implementation(Deps.Badoo.Reaktive.reaktive)
}
}
named("commonTest") {
dependencies {
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain)
implementation(Deps.Badoo.Reaktive.reaktiveTesting)
implementation(Deps.Badoo.Reaktive.utils)
}
}
}
}

11
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/ListOutputToOutput.kt

@ -1,11 +0,0 @@
package example.todo.common.main.integration
import example.todo.common.list.TodoList
import example.todo.common.main.TodoMain.Output
internal val listOutputToOutput: TodoList.Output.() -> Output? =
{
when (this) {
is TodoList.Output.ItemSelected -> Output.Selected(id = id)
}
}

123
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainImpl.kt

@ -2,58 +2,123 @@ package example.todo.common.main.integration
import androidx.compose.foundation.Box
import androidx.compose.foundation.Text
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.Divider
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.ExperimentalKeyInput
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.keyInputFilter
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.child
import com.arkivanov.mvikotlin.core.binder.BinderLifecycleMode
import com.badoo.reaktive.base.Consumer
import com.badoo.reaktive.observable.mapNotNull
import com.badoo.reaktive.subject.publish.PublishSubject
import example.todo.common.add.TodoAdd
import example.todo.common.list.TodoList
import example.todo.common.main.TodoMain
import example.todo.common.main.TodoMain.Dependencies
import example.todo.common.utils.bind
import example.todo.common.main.TodoMain.Output
import example.todo.common.main.store.TodoItem
import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State
import example.todo.common.main.store.TodoMainStoreProvider
import example.todo.common.utils.composeState
import example.todo.common.utils.getStore
import example.todo.common.utils.onKeyUp
internal class TodoMainImpl(
componentContext: ComponentContext,
dependencies: Dependencies
) : TodoMain, ComponentContext by componentContext, Dependencies by dependencies {
private val listOutput = PublishSubject<TodoList.Output>()
private val store =
instanceKeeper.getStore {
TodoMainStoreProvider(
storeFactory = storeFactory,
database = TodoMainStoreDatabase(queries = database.todoDatabaseQueries)
).provide()
}
internal val state: State get() = store.state
@Composable
override fun invoke() {
val state by store.composeState
Column {
TopAppBar(title = { Text(text = "Todo List") })
private val todoList =
TodoList(
componentContext = child(key = "TodoList"),
dependencies = object : TodoList.Dependencies, Dependencies by dependencies {
override val listOutput: Consumer<TodoList.Output> = this@TodoMainImpl.listOutput
Box(Modifier.weight(1F)) {
TodoList(items = state.items)
}
)
private val todoAdd =
TodoAdd(
componentContext = child(key = "TodoAdd"),
dependencies = object : TodoAdd.Dependencies, Dependencies by dependencies {}
)
TodoInput(text = state.text)
}
}
@Composable
private fun TodoList(items: List<TodoItem>) {
LazyColumnFor(items = items) { item ->
Row(modifier = Modifier.clickable(onClick = { onItemClicked(id = item.id) }).padding(8.dp)) {
Text(
text = AnnotatedString(item.text),
modifier = Modifier.weight(1F),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(8.dp))
init {
bind(BinderLifecycleMode.START_STOP) {
listOutput.mapNotNull(listOutputToOutput) bindTo mainOutput
Checkbox(
checked = item.isDone,
onCheckedChange = { onDoneChanged(id = item.id, isDone = it) }
)
}
Divider()
}
}
@OptIn(ExperimentalKeyInput::class)
@Composable
override fun invoke() {
Column {
TopAppBar(title = { Text(text = "Todo List") })
private fun TodoInput(text: String) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp)) {
OutlinedTextField(
value = text,
modifier = Modifier.weight(weight = 1F).keyInputFilter(onKeyUp(Key.Enter, ::onAddClicked)),
onValueChange = ::onTextChanged,
label = { Text(text = "Add a todo") }
)
Box(Modifier.weight(1F)) {
todoList()
Button(modifier = Modifier.padding(start = 8.dp), onClick = ::onAddClicked) {
Text(text = "+")
}
todoAdd()
}
}
internal fun onItemClicked(id: Long) {
mainOutput.onNext(Output.Selected(id = id))
}
internal fun onDoneChanged(id: Long, isDone: Boolean) {
store.accept(Intent.SetItemDone(id = id, isDone = isDone))
}
internal fun onTextChanged(text: String) {
store.accept(Intent.SetText(text = text))
}
internal fun onAddClicked() {
store.accept(Intent.AddItem)
}
}

23
examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListStoreDatabase.kt → examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainStoreDatabase.kt

@ -1,7 +1,7 @@
package example.todo.common.list.integration
package example.todo.common.main.integration
import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.completable.completable
import com.badoo.reaktive.completable.completableFromFunction
import com.badoo.reaktive.completable.subscribeOn
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.mapIterable
@ -10,12 +10,12 @@ import com.squareup.sqldelight.Query
import example.todo.common.database.TodoDatabaseQueries
import example.todo.common.database.TodoItemEntity
import example.todo.common.database.asObservable
import example.todo.common.list.store.TodoItem
import example.todo.common.list.store.TodoListStoreProvider.Database
import example.todo.common.main.store.TodoItem
import example.todo.common.main.store.TodoMainStoreProvider
internal class TodoListStoreDatabase(
internal class TodoMainStoreDatabase(
private val queries: TodoDatabaseQueries
) : Database {
) : TodoMainStoreProvider.Database {
override val updates: Observable<List<TodoItem>> =
queries
@ -32,6 +32,15 @@ internal class TodoListStoreDatabase(
)
override fun setDone(id: Long, isDone: Boolean): Completable =
completable { queries.setDone(id = id, isDone = isDone) }
completableFromFunction { queries.setDone(id = id, isDone = isDone) }
.subscribeOn(ioScheduler)
override fun add(text: String): Completable =
completableFromFunction {
queries.transactionWithResult {
queries.add(text = text)
val lastId = queries.getLastInsertId().executeAsOne()
queries.select(id = lastId).executeAsOne()
}
}.subscribeOn(ioScheduler)
}

2
examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/store/TodoItem.kt → examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoItem.kt

@ -1,4 +1,4 @@
package example.todo.common.list.store
package example.todo.common.main.store
internal data class TodoItem(
val id: Long = 0L,

19
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStore.kt

@ -0,0 +1,19 @@
package example.todo.common.main.store
import com.arkivanov.mvikotlin.core.store.Store
import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State
internal interface TodoMainStore : Store<Intent, State, Nothing> {
sealed class Intent {
data class SetItemDone(val id: Long, val isDone: Boolean) : Intent()
data class SetText(val text: String) : Intent()
object AddItem : Intent()
}
data class State(
val items: List<TodoItem> = emptyList(),
val text: String = ""
)
}

33
examples/todoapp/common/list/src/commonMain/kotlin/example/todo/common/list/store/TodoListStoreProvider.kt → examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStoreProvider.kt

@ -1,4 +1,4 @@
package example.todo.common.list.store
package example.todo.common.main.store
import com.arkivanov.mvikotlin.core.store.Reducer
import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
@ -10,16 +10,16 @@ import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.observeOn
import com.badoo.reaktive.scheduler.mainScheduler
import example.todo.common.list.store.TodoListStore.Intent
import example.todo.common.list.store.TodoListStore.State
import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State
internal class TodoListStoreProvider(
internal class TodoMainStoreProvider(
private val storeFactory: StoreFactory,
private val database: Database
) {
fun provide(): TodoListStore =
object : TodoListStore, Store<Intent, State, Nothing> by storeFactory.create(
fun provide(): TodoMainStore =
object : TodoMainStore, Store<Intent, State, Nothing> by storeFactory.create(
name = "TodoListStore",
initialState = State(),
bootstrapper = SimpleBootstrapper(Unit),
@ -29,7 +29,8 @@ internal class TodoListStoreProvider(
private sealed class Result {
data class ItemsLoaded(val items: List<TodoItem>) : Result()
data class DoneChanged(val id: Long, val isDone: Boolean) : Result()
data class ItemDoneChanged(val id: Long, val isDone: Boolean) : Result()
data class TextChanged(val text: String) : Result()
}
private inner class ExecutorImpl : ReaktiveExecutor<Intent, Unit, State, Result, Nothing>() {
@ -43,20 +44,28 @@ internal class TodoListStoreProvider(
override fun executeIntent(intent: Intent, getState: () -> State): Unit =
when (intent) {
is Intent.SetDone -> setDone(id = intent.id, isDone = intent.isDone)
is Intent.SetItemDone -> setItemDone(id = intent.id, isDone = intent.isDone)
is Intent.SetText -> dispatch(Result.TextChanged(text = intent.text))
is Intent.AddItem -> addItem(state = getState())
}
private fun setDone(id: Long, isDone: Boolean) {
dispatch(Result.DoneChanged(id = id, isDone = isDone))
private fun setItemDone(id: Long, isDone: Boolean) {
dispatch(Result.ItemDoneChanged(id = id, isDone = isDone))
database.setDone(id = id, isDone = isDone).subscribeScoped()
}
private fun addItem(state: State) {
dispatch(Result.TextChanged(text = ""))
database.add(text = state.text).subscribeScoped()
}
}
private object ReducerImpl : Reducer<State, Result> {
override fun State.reduce(result: Result): State =
when (result) {
is Result.ItemsLoaded -> copy(items = result.items.sorted())
is Result.DoneChanged -> update(id = result.id) { copy(isDone = result.isDone) }
is Result.ItemDoneChanged -> update(id = result.id) { copy(isDone = result.isDone) }
is Result.TextChanged -> copy(text = result.text)
}
private inline fun State.update(id: Long, func: TodoItem.() -> TodoItem): State {
@ -79,5 +88,7 @@ internal class TodoListStoreProvider(
val updates: Observable<List<TodoItem>>
fun setDone(id: Long, isDone: Boolean): Completable
fun add(text: String): Completable
}
}

43
examples/todoapp/common/list/src/commonTest/kotlin/example/todo/common/list/integration/TodoListTest.kt → examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/integration/TodoMainTest.kt

@ -1,4 +1,4 @@
package example.todo.common.list.integration
package example.todo.common.main.integration
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.decompose.lifecycle.LifecycleRegistry
@ -11,9 +11,10 @@ import com.badoo.reaktive.test.observable.assertValue
import com.badoo.reaktive.test.observable.test
import com.badoo.reaktive.test.scheduler.TestScheduler
import example.todo.common.database.TestDatabaseDriver
import example.todo.common.list.TodoList.Dependencies
import example.todo.common.list.TodoList.Output
import example.todo.common.list.store.TodoItem
import example.todo.common.database.TodoItemEntity
import example.todo.common.main.TodoMain.Dependencies
import example.todo.common.main.TodoMain.Output
import example.todo.common.main.store.TodoItem
import example.todo.database.TodoDatabase
import kotlin.test.BeforeTest
import kotlin.test.Test
@ -22,7 +23,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class TodoListTest {
class TodoMainTest {
private val lifecycle = LifecycleRegistry()
private val database = TodoDatabase(TestDatabaseDriver())
@ -32,12 +33,12 @@ class TodoListTest {
private val queries = database.todoDatabaseQueries
private val impl by lazy {
TodoListImpl(
TodoMainImpl(
componentContext = DefaultComponentContext(lifecycle = lifecycle),
dependencies = object : Dependencies {
override val storeFactory: StoreFactory = DefaultStoreFactory
override val database: TodoDatabase = this@TodoListTest.database
override val listOutput: Consumer<Output> = outputSubject
override val database: TodoDatabase = this@TodoMainTest.database
override val mainOutput: Consumer<Output> = outputSubject
}
)
}
@ -58,13 +59,13 @@ class TodoListTest {
}
@Test
fun WHEN_item_clicked_THEN_Output_ItemSelected_emitted() {
fun WHEN_item_clicked_THEN_Output_Selected_emitted() {
queries.add("Item1")
val id = firstItem().id
impl.onItemClicked(id = id)
output.assertValue(Output.ItemSelected(id = id))
output.assertValue(Output.Selected(id = id))
}
@Test
@ -99,5 +100,27 @@ class TodoListTest {
assertEquals("New text", firstItem().text)
}
@Test
fun WHEN_text_changed_THEN_text_updated() {
impl.onTextChanged(text = "Item text")
assertEquals("Item text", impl.state.text)
}
@Test
fun GIVEN_text_entered_WHEN_add_clicked_THEN_item_added_in_database() {
impl.onTextChanged(text = "Item text")
impl.onAddClicked()
assertEquals("Item text", lastInsertItem().text)
}
private fun firstItem(): TodoItem = impl.state.items[0]
private fun lastInsertItem(): TodoItemEntity {
val lastInsertId = queries.getLastInsertId().executeAsOne()
return queries.select(id = lastInsertId).executeAsOne()
}
}

13
examples/todoapp/common/list/src/commonTest/kotlin/example/todo/common/list/store/TestTodoListStoreDatabase.kt → examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TestTodoMainStoreDatabase.kt

@ -1,12 +1,13 @@
package example.todo.common.list.store
package example.todo.common.main.store
import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.completable.completableFromFunction
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.subject.behavior.BehaviorSubject
import example.todo.common.list.store.TodoListStoreProvider.Database
import example.todo.common.main.store.TodoItem
import example.todo.common.main.store.TodoMainStoreProvider
internal class TestTodoListStoreDatabase : Database {
internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database {
private val subject = BehaviorSubject<List<TodoItem>>(emptyList())
@ -23,6 +24,12 @@ internal class TestTodoListStoreDatabase : Database {
update(id = id) { copy(isDone = isDone) }
}
override fun add(text: String): Completable =
completableFromFunction {
val id = items.maxBy(TodoItem::id)?.id?.inc() ?: 1L
this.items += TodoItem(id = id, order = id, text = text)
}
private fun update(id: Long, func: TodoItem.() -> TodoItem) {
items = items.map { if (it.id == id) it.func() else it }
}

42
examples/todoapp/common/list/src/commonTest/kotlin/example/todo/common/list/store/TodoListStoreTest.kt → examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TodoMainStoreTest.kt

@ -1,18 +1,19 @@
package example.todo.common.list.store
package example.todo.common.main.store
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.badoo.reaktive.scheduler.overrideSchedulers
import com.badoo.reaktive.test.scheduler.TestScheduler
import example.todo.common.main.store.TodoMainStore.Intent
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class TodoListStoreTest {
class TodoMainStoreTest {
private val database = TestTodoListStoreDatabase()
private val provider = TodoListStoreProvider(storeFactory = DefaultStoreFactory, database = database)
private val database = TestTodoMainStoreDatabase()
private val provider = TodoMainStoreProvider(storeFactory = DefaultStoreFactory, database = database)
@BeforeTest
fun before() {
@ -45,14 +46,43 @@ class TodoListStoreTest {
}
@Test
fun WHEN_Intent_setDone_THEN_done_changed_in_state() {
fun WHEN_Intent_SetItemDone_THEN_done_changed_in_state() {
val item1 = TodoItem(id = 1L, text = "item1")
val item2 = TodoItem(id = 2L, text = "item2", isDone = false)
database.items = listOf(item1, item2)
val store = provider.provide()
store.accept(TodoListStore.Intent.SetDone(id = 2L, isDone = true))
store.accept(Intent.SetItemDone(id = 2L, isDone = true))
assertTrue(store.state.items.first { it.id == 2L }.isDone)
}
@Test
fun WHEN_Intent_SetText_WHEN_text_changed_in_state() {
val store = provider.provide()
store.accept(Intent.SetText(text = "Item text"))
assertEquals("Item text", store.state.text)
}
@Test
fun GIVEN_text_entered_WHEN_Intent_AddItem_THEN_item_added_in_database() {
val store = provider.provide()
store.accept(Intent.SetText(text = "Item text"))
store.accept(Intent.AddItem)
assertTrue(database.items.any { it.text == "Item text" })
}
@Test
fun GIVEN_text_entered_WHEN_Intent_AddItem_THEN_text_cleared_in_state() {
val store = provider.provide()
store.accept(Intent.SetText(text = "Item text"))
store.accept(Intent.AddItem)
assertEquals("", store.state.text)
}
}

2
examples/todoapp/settings.gradle.kts

@ -1,8 +1,6 @@
include(
":common:utils",
":common:database",
":common:list",
":common:add",
":common:main",
":common:edit",
":common:root",

Loading…
Cancel
Save