diff --git a/app/build.gradle b/app/build.gradle index 6e1fe20..90ce67a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,11 +23,11 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0-alpha05' + implementation 'androidx.appcompat:appcompat:1.1.0-rc01' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation "io.reactivex.rxjava2:rxjava:2.2.8" implementation "io.reactivex.rxjava2:rxkotlin:2.3.0" @@ -37,5 +37,5 @@ dependencies { implementation "org.jetbrains.anko:anko:$anko_version" implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0' - implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha05' + implementation 'androidx.recyclerview:recyclerview:1.1.0-beta03' } diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/MainActivity.kt b/app/src/main/java/com/github/skyfe79/android/library/app/MainActivity.kt index c8c95a6..a3604c0 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/MainActivity.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/MainActivity.kt @@ -4,7 +4,6 @@ package com.github.skyfe79.android.library.app import androidx.lifecycle.ViewModelProviders import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import com.github.skyfe79.android.library.app.action.ResetRouteAction import com.github.skyfe79.android.library.app.examples.collectionview.CollectionViewActivity import com.github.skyfe79.android.library.app.examples.counter.CounterActivity import com.github.skyfe79.android.library.app.examples.counter.CounterActivity2 @@ -40,11 +39,6 @@ class MainActivity : AppCompatActivity() { .disposedBy(disposeBag) } - override fun onPause() { - super.onPause() - viewModel.dispatch(ResetRouteAction) - } - private fun handleRoute(route: MainRoute) = when (route) { MainRoute.CounterExample -> startActivity() MainRoute.CounterExample2 -> startActivity() diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt b/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt index fd58b5f..f406d8b 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt @@ -4,7 +4,6 @@ import com.github.skyfe79.android.reactcomponentkit.redux.Output import com.github.skyfe79.android.reactcomponentkit.redux.State import com.github.skyfe79.android.reactcomponentkit.viewmodel.RootViewModelType import com.github.skyfe79.android.library.app.redux.routeReducer -import com.github.skyfe79.android.library.app.redux.reset enum class MainRoute { None, @@ -23,13 +22,11 @@ class MainViewModel: RootViewModelType() { override fun setupStore() { store.set( initialState = MainState(MainRoute.None), - middlewares = arrayOf(::reset), - reducers = arrayOf(::routeReducer), - postwares = arrayOf() + reducers = arrayOf(::routeReducer) ) } override fun on(newState: MainState) { - route.acceptWithoutCompare(newState.route) + route.accept(newState.route).afterReset(MainRoute.None) } } \ No newline at end of file diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/action/Actions.kt b/app/src/main/java/com/github/skyfe79/android/library/app/action/Actions.kt index 292c15a..aa8dde5 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/action/Actions.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/action/Actions.kt @@ -2,7 +2,6 @@ package com.github.skyfe79.android.library.app.action import com.github.skyfe79.android.reactcomponentkit.redux.Action -object ResetRouteAction: Action object ClickCounterExampleButtonAction: Action object ClickCounterExample2ButtonAction: Action object ClickRecyclerViewExampleButtonAction: Action diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt index 092499a..0ac7c22 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt @@ -1,6 +1,5 @@ package com.github.skyfe79.android.library.app.examples.emojicollection -import com.github.skyfe79.android.library.app.examples.emojicollection.middlewares.reset import com.github.skyfe79.android.library.app.examples.emojicollection.middlewares.route import com.github.skyfe79.android.library.app.examples.emojicollection.postwares.* import com.github.skyfe79.android.library.app.examples.emojicollection.reducers.addEmoji @@ -30,7 +29,7 @@ class EmojiCollectionViewModel: RootViewModelType() { override fun setupStore() { store.set( initialState = EmojiCollectionState(listOf(), listOf()), - middlewares = arrayOf(::reset, ::route), + middlewares = arrayOf(::route), reducers = arrayOf(::addEmoji, ::removeEmoji, ::shuffleEmoji), postwares = arrayOf(::makeItemModels) ) @@ -38,6 +37,6 @@ class EmojiCollectionViewModel: RootViewModelType() { override fun on(newState: EmojiCollectionState) { itemModels.accept(newState.itemModels) - routes.accept(newState.route) + routes.accept(newState.route).afterReset(EmojiRoute.None) } } \ No newline at end of file diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/actions/Actions.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/actions/Actions.kt index 5511804..a9312b1 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/actions/Actions.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/actions/Actions.kt @@ -4,4 +4,5 @@ import com.github.skyfe79.android.reactcomponentkit.redux.Action data class AddEmojiAction(val emoji: String): Action object RemoveEmojiAction: Action -object ShuffleEmojiAction: Action \ No newline at end of file +object ShuffleEmojiAction: Action +object MakeItemModelsAction: Action \ No newline at end of file diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/afters/Reset.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/afters/Reset.kt new file mode 100644 index 0000000..f461e4c --- /dev/null +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/afters/Reset.kt @@ -0,0 +1,8 @@ +package com.github.skyfe79.android.library.app.examples.emojicollection.afters + +import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionState +import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiRoute + +fun reset(state: EmojiCollectionState): EmojiCollectionState { + return state.copy(route = EmojiRoute.None) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/middlewares/Reset.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/middlewares/Reset.kt deleted file mode 100644 index 7347e6f..0000000 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/middlewares/Reset.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.skyfe79.android.library.app.examples.emojicollection.middlewares - -import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionState -import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionViewModel -import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiRoute -import com.github.skyfe79.android.reactcomponentkit.redux.Action -import com.github.skyfe79.android.reactcomponentkit.redux.State -import io.reactivex.Observable - -fun EmojiCollectionViewModel.reset(state: State, action: Action): Observable { - val emojiCollectionState = (state as? EmojiCollectionState) ?: return Observable.just(state) - return Observable.just(emojiCollectionState.copy(route = EmojiRoute.None)) -} \ No newline at end of file diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/postwares/Postwares.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/postwares/Postwares.kt index 1bf4c84..d101a9d 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/postwares/Postwares.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/postwares/Postwares.kt @@ -3,6 +3,7 @@ package com.github.skyfe79.android.library.app.examples.emojicollection.postware import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionState import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionViewModel import com.github.skyfe79.android.library.app.examples.emojicollection.actions.AddEmojiAction +import com.github.skyfe79.android.library.app.examples.emojicollection.actions.MakeItemModelsAction import com.github.skyfe79.android.library.app.examples.emojicollection.actions.RemoveEmojiAction import com.github.skyfe79.android.library.app.examples.emojicollection.actions.ShuffleEmojiAction import com.github.skyfe79.android.library.app.examples.emojicollection.models.EmojiBoxModel @@ -14,7 +15,7 @@ fun EmojiCollectionViewModel.makeItemModels(state: State, action: Action): Obser val emojiCollectionState = (state as? EmojiCollectionState) ?: return Observable.just(state) return when (action) { - is AddEmojiAction, RemoveEmojiAction, ShuffleEmojiAction -> { + is MakeItemModelsAction -> { val emojiBoxModels = emojiCollectionState.emojis.map { EmojiBoxModel(it) } val mutatedState = emojiCollectionState.copy(itemModels = emojiBoxModels) Observable.just(mutatedState) diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/reducers/Reducers.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/reducers/Reducers.kt index b071846..4d27190 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/reducers/Reducers.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/reducers/Reducers.kt @@ -3,23 +3,46 @@ package com.github.skyfe79.android.library.app.examples.emojicollection.reducers import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionState import com.github.skyfe79.android.library.app.examples.emojicollection.EmojiCollectionViewModel import com.github.skyfe79.android.library.app.examples.emojicollection.actions.AddEmojiAction +import com.github.skyfe79.android.library.app.examples.emojicollection.actions.MakeItemModelsAction import com.github.skyfe79.android.library.app.examples.emojicollection.actions.RemoveEmojiAction import com.github.skyfe79.android.library.app.examples.emojicollection.actions.ShuffleEmojiAction import com.github.skyfe79.android.library.app.examples.emojicollection.util.EmojiHelper import com.github.skyfe79.android.reactcomponentkit.redux.Action import com.github.skyfe79.android.reactcomponentkit.redux.State import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.rxkotlin.subscribeBy +import java.lang.Exception +import kotlin.random.Random + +fun asyncAPI(): Single { + return Single.create { + Thread.sleep(500L) + it.onSuccess(1) + } +} fun EmojiCollectionViewModel.addEmoji(state: State, action: Action): Observable { val emojiCollectionState = (state as? EmojiCollectionState) ?: return Observable.just(state) return when (action) { is AddEmojiAction -> { - val mutableEmojiList = emojiCollectionState.emojis.toMutableList() - val index = if (mutableEmojiList.isEmpty()) 0 else (0 until mutableEmojiList.size).random() - mutableEmojiList.add(index, EmojiHelper.emoji) - val mutatedState = emojiCollectionState.copy(emojis = mutableEmojiList) - Observable.just(mutatedState) + Single.create { emitter -> + asyncAPI().subscribeBy( + onSuccess = { + val mutableEmojiList = emojiCollectionState.emojis.toMutableList() + val index = if (mutableEmojiList.isEmpty()) 0 else (0 until mutableEmojiList.size).random() + mutableEmojiList.add(index, EmojiHelper.emoji) + emitter.onSuccess(emojiCollectionState.copy(emojis = mutableEmojiList)) + nextDispatch(MakeItemModelsAction, applyNewState = true) + //nextDispatch(AddEmojiAction(EmojiHelper.emoji), applyNewState = true) + }, + onError = { + emitter.onError(it) + } + ) + }.toObservable() + } else -> Observable.just(state) } @@ -31,11 +54,17 @@ fun EmojiCollectionViewModel.removeEmoji(state: State, action: Action): Observab return when (action) { is RemoveEmojiAction -> { - val mutableEmojiList = emojiCollectionState.emojis.toMutableList() - val index = if (mutableEmojiList.isEmpty()) 0 else (0 until mutableEmojiList.size).random() - mutableEmojiList.removeAt(index) - val mutatedState = emojiCollectionState.copy(emojis = mutableEmojiList) - Observable.just(mutatedState) + return try { + val mutableEmojiList = emojiCollectionState.emojis.toMutableList() + val index = if (mutableEmojiList.isEmpty()) 0 else (0 until mutableEmojiList.size).random() + mutableEmojiList.removeAt(index) + val mutatedState = emojiCollectionState.copy(emojis = mutableEmojiList) + nextDispatch(MakeItemModelsAction, applyNewState = true) + Observable.just(mutatedState) + } catch (e: Exception) { + // ignore + Observable.just(state) + } } else -> Observable.just(state) } @@ -49,6 +78,7 @@ fun EmojiCollectionViewModel.shuffleEmoji(state: State, action: Action): Observa val mutableEmojiList = emojiCollectionState.emojis.toMutableList() mutableEmojiList.shuffle() val mutatedState = emojiCollectionState.copy(emojis = mutableEmojiList) + nextDispatch(MakeItemModelsAction, applyNewState = true) Observable.just(mutatedState) } else -> Observable.just(state) diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/redux/Middlewares.kt b/app/src/main/java/com/github/skyfe79/android/library/app/redux/Middlewares.kt deleted file mode 100644 index 88fed0e..0000000 --- a/app/src/main/java/com/github/skyfe79/android/library/app/redux/Middlewares.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.skyfe79.android.library.app.redux - -import com.github.skyfe79.android.library.app.MainRoute -import com.github.skyfe79.android.library.app.MainState -import com.github.skyfe79.android.library.app.MainViewModel -import com.github.skyfe79.android.reactcomponentkit.redux.Action -import com.github.skyfe79.android.reactcomponentkit.redux.State -import io.reactivex.Observable - -fun MainViewModel.reset(state: State, action: Action): Observable { - val mainState = (state as? MainState) ?: return Observable.just(state) - - val mutatedState = mainState.copy(route = MainRoute.None) - - return Observable.just(mutatedState) -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 78b3ecd..cd059f3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.31' + ext.kotlin_version = '1.3.41' ext.lifecycleExtensionsVersion = '2.0.0' ext.anko_version = '0.10.8' repositories { @@ -10,7 +10,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5da75b0..7774312 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 13 22:37:32 KST 2019 +#Thu Aug 22 11:37:10 KST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/reactcomponentkit/build.gradle b/reactcomponentkit/build.gradle index efe9cca..050fcf7 100644 --- a/reactcomponentkit/build.gradle +++ b/reactcomponentkit/build.gradle @@ -17,7 +17,7 @@ publish { userOrg = 'skyfe79' groupId = 'com.github.skyfe79.android' artifactId = 'reactcomponentkit' - publishVersion = '1.1.7' + publishVersion = '1.2.7' desc = 'AndroidReactComponentKit = Component + MVVM + Redux for Android!!!' website = 'https://github.com/ReactComponentKit/AndroidReactComponentKit' } @@ -29,8 +29,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 8 - versionName "1.1.7" + versionCode 9 + versionName "1.2.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -52,10 +52,10 @@ tasks.withType(Javadoc).all { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.1.0-alpha05' + implementation 'androidx.appcompat:appcompat:1.1.0-rc01' testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" @@ -67,5 +67,5 @@ dependencies { implementation "org.jetbrains.anko:anko:$anko_version" - implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha05' + implementation 'androidx.recyclerview:recyclerview:1.1.0-beta03' } \ No newline at end of file diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/After.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/After.kt new file mode 100644 index 0000000..50b171e --- /dev/null +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/After.kt @@ -0,0 +1,5 @@ +package com.github.skyfe79.android.reactcomponentkit.redux + +// Utility for something like as reset some state. +// Run it after dispatching new state to Components +typealias After = (S) -> S \ No newline at end of file diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/AndroidViewModelType.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/AndroidViewModelType.kt index 2c39b5a..fd4dccf 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/AndroidViewModelType.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/AndroidViewModelType.kt @@ -1,21 +1,24 @@ package com.github.skyfe79.android.reactcomponentkit.redux import android.app.Application +import android.util.Log import androidx.lifecycle.AndroidViewModel import com.github.skyfe79.android.reactcomponentkit.redux.State import com.jakewharton.rxrelay2.BehaviorRelay import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable +import java.util.concurrent.atomic.AtomicBoolean abstract class AndroidViewModelType(application: Application): AndroidViewModel(application) { private val rx_action: BehaviorRelay = BehaviorRelay.createDefault(VoidAction) private val rx_state: BehaviorRelay = BehaviorRelay.create() val store = Store() - val disposables = CompositeDisposable() + private val disposables = CompositeDisposable() private var nextAction: Action? = null private var applyNewState: Boolean = false private var actionQueue: Queue> = Queue() + private var isProcessingAction: AtomicBoolean = AtomicBoolean(false) init { setupRxStream() @@ -35,12 +38,28 @@ abstract class AndroidViewModelType(application: Application): Android private fun setupRxStream() { val disposable1 = rx_action + .doOnNext { + isProcessingAction.set(true) + } .filter { action -> - action !is VoidAction + if (action !is VoidAction) { + true + } else { + isProcessingAction.set(false) + false + } } .map { action -> beforeDispatch(action) } + .filter { action -> + if (action !is VoidAction) { + true + } else { + isProcessingAction.set(false) + false + } + } .flatMap { action -> store.dispatch(action).toObservable() } @@ -55,22 +74,34 @@ abstract class AndroidViewModelType(application: Application): Android disposables.add(disposable1) val disposable2 = rx_state + .observeOn(AndroidSchedulers.mainThread()) + .doAfterNext { + isProcessingAction.set(false) + } + .doOnError { + isProcessingAction.set(false) + } .subscribe { newState -> if (newState == null) return@subscribe if (newState.error != null) { newState.error?.let { on(it) + store.doAfters() } } else { - val actionItem = actionQueue.dequeue() + val actionItem = actionQueue.peek() if (actionItem != null) { val (nextAction, apply) = actionItem if (apply) { on(newState) + store.doAfters() } rx_action.accept(nextAction) + // deque actions after processing + actionQueue.dequeue() } else { on(newState) + store.doAfters() } } } @@ -80,18 +111,34 @@ abstract class AndroidViewModelType(application: Application): Android abstract fun setupStore() fun dispatch(action: Action) { - if (actionQueue.isEmpty) { - rx_action.accept(action) + if (actionQueue.isNotEmpty) { + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, applyNewState)) + } else { + rx_action.accept(action) + } } else { - actionQueue.enqueue(Pair(action, true)) + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, applyNewState)) + } else { + rx_action.accept(action) + } } } fun nextDispatch(action: Action, applyNewState: Boolean = false) { - if (actionQueue.isEmpty) { - rx_action.accept(action) + if (actionQueue.isNotEmpty) { + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, applyNewState)) + } else { + rx_action.accept(action) + } } else { - actionQueue.enqueue(Pair(action, applyNewState)) + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, applyNewState)) + } else { + rx_action.accept(action) + } } } diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt index dbf6e4e..6edc120 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt @@ -9,18 +9,25 @@ class Output(defaultValue: T?) { val value: T? get() = behaviorRelay.value - fun accept(value: T) { - val currentValue = behaviorRelay.value - if (currentValue != value) { + fun accept(value: T, withoutCompare: Boolean = false): ResetChanin { + if (withoutCompare) { behaviorRelay.accept(value) + } else { + val currentValue = behaviorRelay.value + if (currentValue != value) { + behaviorRelay.accept(value) + } } - } - - fun acceptWithoutCompare(value: T) { - behaviorRelay.accept(value) + return ResetChanin() } fun asObservable(): Observable { return behaviorRelay } + + inner class ResetChanin { + fun afterReset(value: T) { + accept(value, withoutCompare = true) + } + } } \ No newline at end of file diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Queue.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Queue.kt index 8e1ea75..e03e10b 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Queue.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Queue.kt @@ -11,6 +11,9 @@ class Queue { val isEmpty: Boolean get() = items.isEmpty() + val isNotEmpty: Boolean + get() = items.isNotEmpty() + fun enqueue(item: T) { items.add(item) } @@ -20,4 +23,8 @@ class Queue { } fun clear() = items.clear() + + fun peek(): T? { + return items.peek() + } } \ No newline at end of file diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt index 2e3e76b..a849ad8 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt @@ -4,10 +4,10 @@ import io.reactivex.Observable import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable +import io.reactivex.plugins.RxJavaPlugins import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.toObservable import io.reactivex.schedulers.Schedulers -import kotlin.reflect.KFunction class Store { @@ -16,21 +16,47 @@ class Store { private lateinit var middlewares: Array private lateinit var reducers: Array private lateinit var postwares: Array + private lateinit var afters: Array> private val disposables = CompositeDisposable() + companion object { + init { + RxJavaPlugins.setErrorHandler { + } + } + } + fun set( initialState: S, middlewares: Array = emptyArray(), reducers: Array = emptyArray(), - postwares: Array = emptyArray()) { + postwares: Array = emptyArray(), + afters: Array> = emptyArray()) { this.state = initialState this.middlewares = middlewares this.reducers = reducers this.postwares = postwares + this.afters = afters } fun deinitialize() { + middlewares = emptyArray() + reducers = emptyArray() + postwares = emptyArray() + afters = emptyArray() + disposables.clear() + } + /** + * Do something after dispatching new state. + * For example, reset route on Android + */ + internal fun doAfters() { + var mutatedState = state + afters.forEach { + mutatedState = it(mutatedState) + } + state = mutatedState } @Suppress("UNCHECKED_CAST") diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/ViewModelType.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/ViewModelType.kt index 6cc0ee3..bf53108 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/ViewModelType.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/ViewModelType.kt @@ -1,9 +1,11 @@ package com.github.skyfe79.android.reactcomponentkit.redux +import android.util.Log import androidx.lifecycle.ViewModel import com.jakewharton.rxrelay2.BehaviorRelay import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable +import java.util.concurrent.atomic.AtomicBoolean abstract class ViewModelType: ViewModel() { @@ -11,10 +13,11 @@ abstract class ViewModelType: ViewModel() { private val rx_state: BehaviorRelay = BehaviorRelay.create() val store = Store() - val disposables = CompositeDisposable() + private val disposables = CompositeDisposable() private var nextAction: Action? = null private var applyNewState: Boolean = false private var actionQueue: Queue> = Queue() + private var isProcessingAction: AtomicBoolean = AtomicBoolean(false) init { setupRxStream() @@ -34,12 +37,28 @@ abstract class ViewModelType: ViewModel() { private fun setupRxStream() { val disposable1 = rx_action + .doOnNext { + isProcessingAction.set(true) + } .filter { action -> - action !is VoidAction + if (action !is VoidAction) { + true + } else { + isProcessingAction.set(false) + false + } } .map { action -> beforeDispatch(action) } + .filter { action -> + if (action !is VoidAction) { + true + } else { + isProcessingAction.set(false) + false + } + } .flatMap { action -> store.dispatch(action).toObservable() } @@ -54,22 +73,34 @@ abstract class ViewModelType: ViewModel() { disposables.add(disposable1) val disposable2 = rx_state + .observeOn(AndroidSchedulers.mainThread()) + .doAfterNext { + isProcessingAction.set(false) + } + .doOnError { + isProcessingAction.set(false) + } .subscribe { newState -> if (newState == null) return@subscribe if (newState.error != null) { newState.error?.let { on(it) + store.doAfters() } } else { - val actionItem = actionQueue.dequeue() + val actionItem = actionQueue.peek() if (actionItem != null) { val (nextAction, apply) = actionItem if (apply) { on(newState) + store.doAfters() } rx_action.accept(nextAction) + // deque actions after processing + actionQueue.dequeue() } else { on(newState) + store.doAfters() } } } @@ -79,18 +110,34 @@ abstract class ViewModelType: ViewModel() { abstract fun setupStore() fun dispatch(action: Action) { - if (actionQueue.isEmpty) { - rx_action.accept(action) + if (actionQueue.isNotEmpty) { + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, true)) + } else { + rx_action.accept(action) + } } else { - actionQueue.enqueue(Pair(action, true)) + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, true)) + } else { + rx_action.accept(action) + } } } fun nextDispatch(action: Action, applyNewState: Boolean = false) { - if (actionQueue.isEmpty) { - rx_action.accept(action) + if (actionQueue.isNotEmpty) { + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, applyNewState)) + } else { + rx_action.accept(action) + } } else { - actionQueue.enqueue(Pair(action, applyNewState)) + if (isProcessingAction.get()) { + actionQueue.enqueue(Pair(action, applyNewState)) + } else { + rx_action.accept(action) + } } }