kanawish / android-mvi-sample

Example MVI implementation, based off of Google's architectural samples.
303 stars 56 forks source link

State Change problem #11

Closed nodir96 closed 4 years ago

nodir96 commented 4 years ago

@kanawish Hi. I'm learning MVI pattern from your tutorial. I try to build sample project for learning MVI and I have a bug. When try to change state there is a bug

W/System.err: io.reactivex.exceptions.CompositeException: 1 exceptions occurred. at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:80) at io.reactivex.internal.util.NotificationLite.accept(NotificationLite.java:243) at io.reactivex.internal.operators.observable.ObservableReplay$BoundedReplayBuffer.replay(ObservableReplay.java:698) at io.reactivex.internal.operators.observable.ObservableReplay$ReplayObserver.replayFinal(ObservableReplay.java:412) at io.reactivex.internal.operators.observable.ObservableReplay$ReplayObserver.onError(ObservableReplay.java:377) at io.reactivex.internal.operators.observable.ObservableScanSeed$ScanSeedObserver.onError(ObservableScanSeed.java:118) at io.reactivex.internal.operators.observable.ObservableScanSeed$ScanSeedObserver.onNext(ObservableScanSeed.java:102) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:201) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:255) at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:124) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5254) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) ComposedException 1 : java.lang.IllegalStateException: editorIntent encountered an inconsistent State. [Looking for class uz.cap.loginpagemvi.model.AnotherLoginPageState$Loading but was class uz.cap.loginpagemvi.model.AnotherLoginPageState$Default] at uz.cap.loginpagemvi.intent.LoginPageIntentFactory$Companion$buildCheckLoginState$$inlined$editorIntent$1.invoke(LoginPageIntentFactory.kt:29) at uz.cap.loginpagemvi.intent.LoginPageIntentFactory$Companion$buildCheckLoginState$$inlined$editorIntent$1.invoke(LoginPageIntentFactory.kt:21) at uz.cap.loginpagemvi.intent.IntentKt$intent$1.reduce(Intent.kt:9) at uz.cap.loginpagemvi.model.ModelStore$store$1.apply(ModelStore.kt:15) at uz.cap.loginpagemvi.model.ModelStore$store$1.apply(ModelStore.kt:9) at io.reactivex.internal.operators.observable.ObservableScanSeed$ScanSeedObserver.onNext(ObservableScanSeed.java:98) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:201) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:255) at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:124) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5254) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

this is my intentFactory class LoginPageIntentFactory( private val loginPageModelStore: LoginPageModelStore ) {

fun process(event: LoginViewEvent) {
    loginPageModelStore.process(toIntent(event))
}

private fun toIntent(loginViewEvent: LoginViewEvent): Intent<AnotherLoginPageState> {
    return when (loginViewEvent) {
        LoginViewEvent.LoginClick -> buildCheckLoginState()
    }
}

companion object {

    inline fun <reified S : AnotherLoginPageState> editorIntent(
        crossinline block: S.() -> AnotherLoginPageState
    ): Intent<AnotherLoginPageState> {
        return intent {

            (this as? S)?.block()
                ?: throw IllegalStateException("editorIntent encountered an inconsistent State. [Looking for ${S::class.java} but was ${this.javaClass}]")
        }
    }

    private fun buildCheckLoginState() = editorIntent<AnotherLoginPageState.Loading> {
        loading {
            copy(
                loginEnabled = false,
                passwordEnabled = false,
                loginBtnEnabled = false,
                forgotPasswordEnabled = false,
                progressVisible = true
            )
        }
    }

} }

and this is my state class sealed class AnotherLoginPageState {

object Default : AnotherLoginPageState() {

}

data class Editing(val state: LoginPageElements) : AnotherLoginPageState() {
    fun edit(block: LoginPageElements.() -> LoginPageElements) = copy(state = state.block())
}

data class Loading(val state: LoginPageElements) : AnotherLoginPageState() {
    fun loading(block: LoginPageElements.() -> LoginPageElements) = copy(state = state.block())
}

data class ForgotPassword(val state: LoginPageElements) : AnotherLoginPageState() {
    fun forgot(block: LoginPageElements.() -> LoginPageElements) = copy(state = state.block())
}

}

data class LoginPageElements( var login: String = "", var password: String = "",

var loginEnabled: Boolean = true,
var passwordEnabled: Boolean = true,
var loginBtnEnabled: Boolean = true,
var forgotPasswordEnabled: Boolean = true,
var progressVisible: Boolean = false

)

kanawish commented 4 years ago

You're running into a case where there's an attempt to call the "loading" function in your state machine, but your actual state is still on "Default".

If you look at the sample code here, you'll note that the similar transition here is Closed -> Editing, and that it takes place when Closed.editTask() is called.

It can be helpful to sketch out your state machine on paper, and make sure your transitions are mapped out. Hope this helps!

nodir96 commented 4 years ago

@kanawish thanks!