在没有Mosby的情况下在Android中实现MVI架构

时间:2019-03-03 14:01:18

标签: java android kotlin architecture

我正在尝试在Android中实现MVI体系结构,但不想使用Mosby Library。我想先学习基础知识。

我正在构建一个示例应用程序,在该应用程序中,当我按下按钮时,textview中的文本会发生变化(最初,文本是其他内容)。这是MainActivity和MainPresenter的代码。

class MainActivity : AppCompatActivity(), MainContract.View {

    lateinit var mPresenter: MainContract.Presenter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mPresenter = MainPresenter()
        mPresenter.attachPresenter(this)
        bind()
    }

    @SuppressLint("CheckResult")
    private fun bind() {
        mPresenter.states().subscribe({ state ->
            render(state)
        }, {
            Log.e("error", "Error is: ", it)
            it.printStackTrace()
        })
        mPresenter.addIntents(intents())
    }

    override fun intents(): Observable<MainIntent> {
        return Observable.merge(
            initialIntent(),
            clickIntent()
        )
    }

    override fun render(state: MainViewState) {
        btn_show.isEnabled = state.isEnabledButton
        helloWorldTextView.text = state.message
        loadingIndicator.visibility = if (state.isLoading) View.VISIBLE else View.GONE
    }

    private fun initialIntent(): Observable<MainIntent.InitialIntent> = Observable.just(MainIntent.InitialIntent)

    private fun clickIntent(): Observable<MainIntent.ClickIntent> {
        return btn_show.clicks().map { MainIntent.ClickIntent("Eureka") }
    }


}



class MainPresenter : MainContract.Presenter {

    private val intentsSubject: PublishSubject<MainIntent> = PublishSubject.create()

    override fun states(): Observable<MainViewState> {
        return statesObservable
    }

    private lateinit var view: MainContract.View

    override fun attachPresenter(view: MainContract.View) {
        this.view = view
    }

    @SuppressLint("CheckResult")
    override fun addIntents(intents: Observable<MainIntent>) {
        intents.subscribe(intentsSubject)
    }

    private val reducer =
        BiFunction { previousState: MainViewState, result: MainResult ->
            when (result) {
                is MainResult.InitialResult.InFlight -> previousState.copy(
                    isLoading = true,
                    message = "Initial Result",
                    isEnabledButton = false
                )
                is MainResult.InitialResult.Success -> previousState.copy(
                    isLoading = true,
                    message = "Initial Success",
                    isEnabledButton = true
                )
                is MainResult.InitialResult.Error -> previousState.copy(
                    isLoading = false,
                    message = "Error Initially",
                    isEnabledButton = true
                )

                is MainResult.ClickedResult.Success -> previousState.copy(
                    isLoading = false,
                    message = System.currentTimeMillis().toString(),
                    isEnabledButton = true
                )
                is MainResult.ClickedResult.Error -> previousState.copy(
                    isLoading = false,
                    message = "Error Clicked",
                    isEnabledButton = true
                )
                is MainResult.ClickedResult.InFlight -> previousState.copy(
                    isLoading = true,
                    message = "Clicked In Flight",
                    isEnabledButton = false
                )
            }
        }

    private fun actionFromIntent(intent: MainIntent): MainAction {
        if (intent is MainIntent.InitialIntent) {
            return MainAction.InitialAction
        } else if (intent is MainIntent.ClickIntent) {
            return MainAction.ClickedAction("Hello")
        } else {
            return MainAction.InitialAction
        }
    }

    private var actionProcessor: ObservableTransformer<MainAction, MainResult> = ObservableTransformer { actions ->
        actions.publish { shared ->
            Observable.merge<MainResult>(
                shared.ofType(MainAction.InitialAction::class.java).compose(initialActionProcessor),
                shared.ofType(MainAction.ClickedAction::class.java).compose(clickActionProcessor)
            )
        }
    }

    private val initialActionProcessor =
        ObservableTransformer<MainAction.InitialAction, MainResult.InitialResult> { action: Observable<MainAction.InitialAction> ->
            action.switchMap {
                Observable.just("hello initially")
                    .map { MainResult.InitialResult.Success(it) }
                    .cast(MainResult.InitialResult::class.java)
                    .onErrorReturn { MainResult.InitialResult.Error(it.message!!) }
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .startWith { MainResult.InitialResult.InFlight }
            }
        }

    private val clickActionProcessor =
        ObservableTransformer<MainAction.ClickedAction, MainResult.ClickedResult> { action: Observable<MainAction.ClickedAction> ->
            Observable.just("Click").map { message ->
                MainResult.ClickedResult.Success(message)
            }.cast(MainResult.ClickedResult::class.java)
                .onErrorReturn { MainResult.ClickedResult.Error("Error") }
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .startWith { MainResult.ClickedResult.InFlight }
        }

    private val statesObservable: Observable<MainViewState> = compose()

    private fun compose(): Observable<MainViewState> {
        return intentsSubject
            .map {
                actionFromIntent(it)
            }
            .compose(actionProcessor)
            .scan(MainViewState.idle(), reducer)
            .distinctUntilChanged()
            .replay(1)
            .autoConnect(0)
    }


}

问题是仅触发了Inital事件,而没有其他事件。该代码不响应点击,渲染最初仅被调用一次。

此外,如果我从 actionProcessors 代码中删除了startWith{},它只会响应一次点击。之后,什么也没发生。

有人看到代码问题吗?我已经尝试解决这个问题已有一段时间了。

2 个答案:

答案 0 :(得分:1)

这不是您问题的直接答案。但是,如果您实施以下内容,则可能不会遇到实际上所问的问题,并且将拥有更简单的MVI解决方案。

您可能尝试合并https://speakerdeck.com/jakewharton/the-state-of-managing-state-with-rxjava-devoxx-us-2017http://hannesdorfmann.com/android/mosby3-mvi-1https://medium.com/@oldergod/managing-state-with-rxjava-b0798a6c5757的想法。

在这里看看:https://proandroiddev.com/taming-state-in-android-with-elm-architecture-and-kotlin-part-1-566caae0f706-更简单。第1部分和第2部分应该足够。

我尝试了第一种方法,但被最初的复杂性所排斥。在第二种方法中,您没有Action, Intent, Result,而是Msg。这样做比较容易。

还有一个新的MVI course-但尚未检查。

答案 1 :(得分:0)

我先前的回复:

这不是您问题的直接答案。但是,如果实施下面的内容,则可能不会遇到您实际提出的问题,并且将拥有更简单的MVI解决方案。

您可能尝试合并https://speakerdeck.com/jakewharton/the-state-of-managing-state-with-rxjava-devoxx-us-2017http://hannesdorfmann.com/android/mosby3-mvi-1https://medium.com/@oldergod/managing-state-with-rxjava-b0798a6c5757的想法。

在这里看看:https://proandroiddev.com/taming-state-in-android-with-elm-architecture-and-kotlin-part-1-566caae0f706-更简单。第1部分和第2部分应该足够。

我尝试了第一种方法,但被最初的复杂性所排斥。在第二种方法中,您没有操作,意图,结果,但有消息。这样做比较容易。

还有新的MVI课程-但尚未检查。

当前方法:

我尝试过提到Elm Architecture,但是它并不完整。至少有两个问题:

  1. 一次只有一个请求可以通过队列。一些RxJava 应该做到这一点(groupBy有2个流:ui,背景 可能)。
  2. 并行请求将更新相同的状态,因此您应在DataStates中区分UiState。因此,UI的不同部分的状态不同。

在编写我们意识到的实际修补程序之前,这并不是去ATM的方法:宣布的Composables可以实现MVI技巧:将数据平稳,准确地过渡到UI的特定部分。

免责声明:主持人删除了我的答案,该答案是实际答案。更重要的是,我的移动到评论的答案被减少了,这使其看起来没有完成。这就是为什么该帖子再次出现。亲爱的主持人看完后,可以删除免责声明,谢谢:)