mirror of
https://github.com/YTVanced/VancedManager
synced 2024-11-25 04:35:12 +00:00
improvement/core-mvi small refactoring and update examples
This commit is contained in:
parent
a6e6d732dc
commit
17c5e04143
11 changed files with 253 additions and 158 deletions
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
interface MviFlow<State, Action, SideEffect> {
|
||||
interface MviFlow<State, Action, Modification, SideEffect> {
|
||||
|
||||
fun bindView(
|
||||
view: MviRenderView<State, Action, SideEffect>,
|
||||
|
@ -25,7 +25,7 @@ private class MviFlowImpl<State, Action, Modification, SideEffect>(
|
|||
private val reducer: Reducer<State, Modification>,
|
||||
private val handler: Handler<State, Action, Modification, SideEffect>,
|
||||
scope: CoroutineScope,
|
||||
) : MviFlow<State, Action, SideEffect>,
|
||||
) : MviFlow<State, Action, Modification, SideEffect>,
|
||||
CoroutineScope by scope,
|
||||
Mutex by Mutex() {
|
||||
|
||||
|
@ -70,19 +70,19 @@ private class MviFlowImpl<State, Action, Modification, SideEffect>(
|
|||
private suspend fun Flow<Action>.proceed() =
|
||||
collect { action ->
|
||||
handler.invoke(
|
||||
MutableSharedFlow<Modification>().setState(),
|
||||
MutableSharedFlow<Modification>().subscribeState(),
|
||||
state.value, action, sideEffect
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableSharedFlow<Modification>.setState(): MutableSharedFlow<Modification> {
|
||||
onEach { modification ->
|
||||
withLock {
|
||||
state.value = reducer.invoke(state.value, modification)
|
||||
}
|
||||
}.launchIn(this@MviFlowImpl)
|
||||
return this
|
||||
}
|
||||
private fun MutableSharedFlow<Modification>.subscribeState(): MutableSharedFlow<Modification> =
|
||||
also {
|
||||
onEach { modification ->
|
||||
withLock {
|
||||
state.value = reducer.invoke(state.value, modification)
|
||||
}
|
||||
}.launchIn(this@MviFlowImpl)
|
||||
}
|
||||
}
|
||||
|
||||
fun <State, Action, SideEffect, Modification> MviFlow(
|
||||
|
@ -90,7 +90,7 @@ fun <State, Action, SideEffect, Modification> MviFlow(
|
|||
reducer: Reducer<State, Modification>,
|
||||
handler: Handler<State, Action, Modification, SideEffect>,
|
||||
scope: CoroutineScope
|
||||
): MviFlow<State, Action, SideEffect> = MviFlowImpl(
|
||||
): MviFlow<State, Action, Modification, SideEffect> = MviFlowImpl(
|
||||
initialState = initialState,
|
||||
reducer = reducer,
|
||||
handler = handler,
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package com.vanced.manager.core.mvi
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
abstract class MviFlowContainer<State, Action, Modification, SideEffect> {
|
||||
|
||||
protected abstract val handler: Handler<State, Action, Modification, SideEffect>
|
||||
|
||||
protected abstract val reducer: Reducer<State, Modification>
|
||||
|
||||
fun create(
|
||||
state: State,
|
||||
scope: CoroutineScope
|
||||
): MviFlow<State, Action, SideEffect> =
|
||||
MviFlow(
|
||||
initialState = state,
|
||||
reducer = reducer,
|
||||
handler = handler,
|
||||
scope = scope
|
||||
)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.vanced.manager.core.mvi
|
||||
|
||||
interface MviFlowStore<State, Action, Modification, SideEffect> {
|
||||
|
||||
val store: MviFlow<State, Action, Modification, SideEffect>
|
||||
}
|
|
@ -6,7 +6,7 @@ typealias Handler<State, Action, Modification, SideEffect> =
|
|||
suspend MutableSharedFlow<Modification>.(
|
||||
state: State,
|
||||
action: Action,
|
||||
sideEffect: MutableSharedFlow<SideEffect>
|
||||
sideEffectsFlow: MutableSharedFlow<SideEffect>,
|
||||
) -> Unit
|
||||
|
||||
typealias Reducer<State, Modification> =
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
package example
|
||||
|
||||
import com.vanced.manager.core.mvi.MviRenderView
|
||||
import example.ExampleContainer.Action
|
||||
import example.ExampleContainer.SideEffect
|
||||
import example.ExampleContainer.State
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class Example2Fragment : MviRenderView<State, Action, SideEffect> {
|
||||
|
||||
val lifecycleScope = CoroutineScope(Job())
|
||||
|
||||
val mvi = ExampleContainer.create(
|
||||
state = State.Default,
|
||||
scope = lifecycleScope
|
||||
) // create "store"
|
||||
|
||||
private fun onCreate() {
|
||||
lifecycleScope.launch { // bind view for call render
|
||||
mvi.bindView(view = this@Example2Fragment, scope = this)
|
||||
}
|
||||
lifecycleScope.launch { // bind side effects (single events) for catch on view
|
||||
mvi.bindSideEffects(view = this@Example2Fragment, scope = this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(state: State) {
|
||||
when (state) {
|
||||
State.Default -> {
|
||||
// render view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun actionsFlow(): Flow<Action> = callbackFlow {
|
||||
//generate actions click and other
|
||||
awaitClose()
|
||||
}
|
||||
|
||||
override fun sideEffects(sideEffect: SideEffect) { // single events
|
||||
when (sideEffect) {
|
||||
is SideEffect.ShowToast -> {
|
||||
// Toast.show
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package example
|
||||
|
||||
import com.vanced.manager.core.mvi.Handler
|
||||
import com.vanced.manager.core.mvi.MviFlowContainer
|
||||
import com.vanced.manager.core.mvi.Reducer
|
||||
import example.ExampleContainer.Action
|
||||
import example.ExampleContainer.Modification
|
||||
import example.ExampleContainer.SideEffect
|
||||
import example.ExampleContainer.State
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
object ExampleContainer : MviFlowContainer<State, Action, Modification, SideEffect>() {
|
||||
|
||||
// "single events"
|
||||
sealed class SideEffect {
|
||||
data class ShowToast(val message: String) : SideEffect()
|
||||
}
|
||||
|
||||
// view state
|
||||
sealed class State {
|
||||
object Default : State()
|
||||
}
|
||||
|
||||
// view actions
|
||||
sealed class Action {
|
||||
object Click : Action()
|
||||
}
|
||||
|
||||
// Modification for view
|
||||
sealed class Modification {
|
||||
data class ChangeText(val text: String) : Modification()
|
||||
}
|
||||
|
||||
// handle actions, generate side effects (single events) and send changes (may be viewModel ane change)
|
||||
override val handler: Handler<State, Action, Modification, SideEffect> =
|
||||
{ _: State, action: Action, sideEffect: MutableSharedFlow<SideEffect> ->
|
||||
when (action) {
|
||||
Action.Click -> {
|
||||
sideEffect.emit(SideEffect.ShowToast(""))
|
||||
delay(10)
|
||||
emit(Modification.ChangeText(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle modifications and current state and generate new state for view
|
||||
override val reducer: Reducer<State, Modification> = { _: State, modification: Modification ->
|
||||
when (modification) {
|
||||
is Modification.ChangeText -> {
|
||||
State.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
95
core-mvi/src/main/java/example/ExampleManyStoreFragment.kt
Normal file
95
core-mvi/src/main/java/example/ExampleManyStoreFragment.kt
Normal file
|
@ -0,0 +1,95 @@
|
|||
package example
|
||||
|
||||
import com.vanced.manager.core.mvi.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ExampleManyStoreFragment {
|
||||
|
||||
val lifecycleScope = CoroutineScope(Job())
|
||||
|
||||
private val view1 = getView()
|
||||
private val container1 = getStore()
|
||||
|
||||
private val view2 = getView()
|
||||
private val container2 = getStore()
|
||||
|
||||
private fun onCreate() {
|
||||
lifecycleScope.launch { // bind view for call render
|
||||
container1.store.bindView(view = view1, scope = this)
|
||||
}
|
||||
lifecycleScope.launch { // bind side effects (single events) for catch on view
|
||||
container1.store.bindSideEffects(view = view1, scope = this)
|
||||
}
|
||||
|
||||
lifecycleScope.launch { // bind view for call render
|
||||
container2.store.bindView(view = view2, scope = this)
|
||||
}
|
||||
lifecycleScope.launch { // bind side effects (single events) for catch on view
|
||||
container2.store.bindSideEffects(view = view2, scope = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle actions, generate side effects (single events) and send changes (may be viewModel ane change)
|
||||
private fun createHandler(): Handler<State, Action, Modification, SideEffect> =
|
||||
{ _: State, action: Action, sideEffect: MutableSharedFlow<SideEffect> ->
|
||||
when (action) {
|
||||
Action.Click -> {
|
||||
sideEffect.emit(SideEffect.ShowToast(""))
|
||||
delay(10)
|
||||
emit(Modification.ChangeText(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle modifications and current state and generate new state for view\
|
||||
private fun createReducer(): Reducer<State, Modification> = { _: State, modification: Modification ->
|
||||
when (modification) {
|
||||
is Modification.ChangeText -> {
|
||||
State.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ExampleManyStoreFragment.getStore() = object : MviFlowStore<State, Action, Modification, SideEffect> {
|
||||
|
||||
override val store: MviFlow<State, Action, Modification, SideEffect>
|
||||
get() = MviFlow(
|
||||
initialState = State.Default,
|
||||
reducer = createReducer(),
|
||||
handler = createHandler(),
|
||||
scope = lifecycleScope
|
||||
)// create "store"
|
||||
}
|
||||
|
||||
private fun getView() =
|
||||
object : MviRenderView<State, Action, SideEffect> {
|
||||
override fun render(state: State) {
|
||||
when (state) {
|
||||
State.Default -> {
|
||||
// render view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun actionsFlow(): Flow<Action> = callbackFlow {
|
||||
//generate actions click and other
|
||||
awaitClose()
|
||||
}
|
||||
|
||||
override fun sideEffects(sideEffect: SideEffect) {
|
||||
when (sideEffect) {
|
||||
is SideEffect.ShowToast -> {
|
||||
// Toast.show
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
75
core-mvi/src/main/java/example/ExampleStoreFragment.kt
Normal file
75
core-mvi/src/main/java/example/ExampleStoreFragment.kt
Normal file
|
@ -0,0 +1,75 @@
|
|||
package example
|
||||
|
||||
import com.vanced.manager.core.mvi.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
|
||||
class ExampleStoreFragment :
|
||||
MviRenderView<State, Action, SideEffect>,
|
||||
MviFlowStore<State, Action, Modification, SideEffect> {
|
||||
|
||||
private val lifecycleScope = CoroutineScope(Job())
|
||||
|
||||
// handle actions, generate side effects (single events) and send changes (may be viewModel ane change)
|
||||
private val handler: Handler<State, Action, Modification, SideEffect> =
|
||||
{ _: State, action: Action, sideEffectsFlow: MutableSharedFlow<SideEffect> ->
|
||||
when (action) {
|
||||
Action.Click -> {
|
||||
sideEffectsFlow.emit(SideEffect.ShowToast(""))
|
||||
delay(10)
|
||||
emit(Modification.ChangeText(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle modifications and current state and generate new state for view
|
||||
private val reducer: Reducer<State, Modification> = { _: State, modification: Modification ->
|
||||
when (modification) {
|
||||
is Modification.ChangeText -> {
|
||||
State.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val store: MviFlow<State, Action, Modification, SideEffect> =
|
||||
MviFlow(
|
||||
initialState = State.Default,
|
||||
reducer = reducer,
|
||||
handler = handler,
|
||||
scope = lifecycleScope
|
||||
)// create "store"
|
||||
|
||||
private fun onCreate() {
|
||||
lifecycleScope.launch { // bind view for call render
|
||||
store.bindView(view = this@ExampleStoreFragment, scope = this)
|
||||
}
|
||||
lifecycleScope.launch { // bind side effects (single events) for catch on view
|
||||
store.bindSideEffects(view = this@ExampleStoreFragment, scope = this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(state: State) {
|
||||
when (state) {
|
||||
State.Default -> {
|
||||
// render view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun actionsFlow(): Flow<Action> = callbackFlow {
|
||||
//generate actions click and other
|
||||
awaitClose()
|
||||
}
|
||||
|
||||
override fun sideEffects(sideEffect: SideEffect) { // single events
|
||||
when (sideEffect) {
|
||||
is SideEffect.ShowToast -> {
|
||||
// Toast.show
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,43 @@
|
|||
package example
|
||||
|
||||
import example.ExampleContainer.State
|
||||
import com.vanced.manager.core.mvi.Handler
|
||||
import com.vanced.manager.core.mvi.MviFlow
|
||||
import com.vanced.manager.core.mvi.MviFlowStore
|
||||
import com.vanced.manager.core.mvi.Reducer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
class ExampleViewModel {
|
||||
class ExampleViewModel : MviFlowStore<State, Action, Modification, SideEffect> {
|
||||
|
||||
val viewModelScope = CoroutineScope(Job())
|
||||
private val viewModelScope = CoroutineScope(Job())
|
||||
|
||||
val mvi = ExampleContainer.create(
|
||||
state = State.Default,
|
||||
// handle actions, generate side effects (single events) and send changes (may be viewModel ane change)
|
||||
private val handler: Handler<State, Action, Modification, SideEffect> =
|
||||
{ _: State, action: Action, sideEffectsFlow: MutableSharedFlow<SideEffect> ->
|
||||
when (action) {
|
||||
Action.Click -> {
|
||||
sideEffectsFlow.emit(SideEffect.ShowToast(""))
|
||||
delay(10)
|
||||
emit(Modification.ChangeText(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle modifications and current state and generate new state for view
|
||||
private val reducer: Reducer<State, Modification> = { _: State, modification: Modification ->
|
||||
when (modification) {
|
||||
is Modification.ChangeText -> {
|
||||
State.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val store: MviFlow<State, Action, Modification, SideEffect> = MviFlow(
|
||||
initialState = State.Default,
|
||||
reducer = reducer,
|
||||
handler = handler,
|
||||
scope = viewModelScope
|
||||
) // create "store"
|
||||
) // create "store"
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
package example
|
||||
|
||||
import com.vanced.manager.core.mvi.MviRenderView
|
||||
import example.ExampleContainer.Action
|
||||
import example.ExampleContainer.SideEffect
|
||||
import example.ExampleContainer.State
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -13,18 +10,18 @@ import kotlinx.coroutines.flow.callbackFlow
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class ExampleFragment : MviRenderView<State, Action, SideEffect> {
|
||||
class ExampleWithViewModelFragment : MviRenderView<State, Action, SideEffect> {
|
||||
|
||||
val lifecycleScope = CoroutineScope(Job())
|
||||
private val lifecycleScope = CoroutineScope(Job())
|
||||
|
||||
val viewModel = ExampleViewModel()
|
||||
private val viewModel = ExampleViewModel()
|
||||
|
||||
private fun onCreate() {
|
||||
lifecycleScope.launch {
|
||||
viewModel.mvi.bindView(view = this@ExampleFragment, scope = this)
|
||||
viewModel.store.bindView(view = this@ExampleWithViewModelFragment, scope = this)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.mvi.bindSideEffects(view = this@ExampleFragment, scope = this)
|
||||
viewModel.store.bindSideEffects(view = this@ExampleWithViewModelFragment, scope = this)
|
||||
}
|
||||
}
|
||||
|
25
core-mvi/src/main/java/example/Mvi.kt
Normal file
25
core-mvi/src/main/java/example/Mvi.kt
Normal file
|
@ -0,0 +1,25 @@
|
|||
package example
|
||||
|
||||
// "single events"
|
||||
sealed class SideEffect {
|
||||
|
||||
data class ShowToast(val message: String) : SideEffect()
|
||||
}
|
||||
|
||||
// view state
|
||||
sealed class State {
|
||||
|
||||
object Default : State()
|
||||
}
|
||||
|
||||
// view actions
|
||||
sealed class Action {
|
||||
|
||||
object Click : Action()
|
||||
}
|
||||
|
||||
// Modification for view
|
||||
sealed class Modification {
|
||||
|
||||
data class ChangeText(val text: String) : Modification()
|
||||
}
|
Loading…
Reference in a new issue