简体   繁体   English

MVI架构中的单个时间事件

[英]Single time events in MVI architecture

Trying new architecture paradigm where presenter creates immutable state(model) stream and view just renders it. 尝试新的体系结构范例,其中演示者创建不可变状态(模型)流和视图只是呈现它。

Can't understand how to handle situations where we need to make some event for the one time only. 无法理解如何处理我们只需要一次制作一些事件的情况。 There are couple examples. 有几个例子。

1) Notes app. 1)笔记应用程序。 We have editText and saveButton . 我们有editTextsaveButton User clicks saveButton , some processing happens and editText should be cleared. 用户单击saveButton ,会发生一些处理并清除editText。 Could you guys please describe what will be in our ViewState here and approximate logic flow? 你们可以在这里描述我们的ViewState并估算逻辑流程吗?

Questions and pitfalls I see now: 我现在看到的问题和陷阱:

  1. We subscribe to editText.textChanges() in presenter. 我们在演示者中订阅了editText.textChanges() If we will have text in our ViewState and render it each render call then we will fall into recursion because it will emit new textChange and will update state and render again. 如果我们在ViewStatetext并为每个渲染调用渲染它,那么我们将进入递归,因为它将发出新的textChange并将更新状态并再次渲染。
  2. Do we need text in ViewState to restore it on orientation text or process kill/restore, looks like it works out of box here. 我们是否需要ViewState text在方向文本或进程终止/恢复时恢复它,看起来它在这里开箱即用。 But imagine recyclerView s scroll position. 但想象一下recyclerView的滚动位置。 We definitely need to save it to restore. 我们肯定需要保存它才能恢复。 We can't restore it on each render call cause it looks weird, isn't it? 我们无法在每次渲染调用时恢复它,因为它看起来很奇怪,不是吗?
  3. If we consider such logic as side effect and will call .doOnNext{ view.clearText() } it makes sense, but do we have reference to view in canonical MVI implementation? 如果我们将这样的逻辑视为副作用并将调用.doOnNext{ view.clearText() }它是有道理的,但我们是否在规范MVI实现中引用了视图? Mosby doesn't have it as I see. 正如我所见,莫斯比没有它。
  4. It makes sense but there is possibility of view being dead in the moment of doOnNext call. 这是有道理的但是在doOnNext调用的那一刻有可能看到死亡。 MVI should help us with this but only if it's the part of ViewState , right? MVI应该帮助我们,但只有它是ViewState的一部分,对吧?

2) Github app. 2)Github应用程序。 First screen(Org): orgEditText , okButton , progressBar . 第一个屏幕(组织): orgEditTextokButtonprogressBar Second screen (Repos): recyclerView . 第二个屏幕(Repos): recyclerView When user enters organization into orgEditText and clicks okButton app should make request to API and on success navigate to Repos screen on success or show toast on failure. 当用户将组织输入orgEditText并单击okButton app时,应向API发出请求,并在成功时导航到Repos屏幕成功或在失败时显示toast。 Again could you please describe ViewState for Org screen and what logic should look like? 再次请你描述一下Org的ViewState屏幕以及应该是什么样的逻辑?

Questions and pitfalls I see now: 我现在看到的问题和陷阱:

  1. We should show progressBar and disable okButton while loading. 我们应该在加载时显示progressBar并禁用okButton We should have like loading/content/error sealed class(lets call it ContentState ) and have its instance in our ViewState . 我们应该喜欢加载/内容/错误密封类(让我们称之为ContentState )并将其实例放在我们的ViewState View knows how to render ContentState.loading and shows progressBar and disables okButton . View知道如何呈现ContentState.loading并显示progressBar并禁用okButton Am I right? 我对吗?
  2. How to handle navigation then? 如何处理导航呢? The same questions as 1.3 and 1.4. 与1.3和1.4相同的问题。
  3. I've seen opinions that navigation should be considered as side effect, but again 1.4. 我已经看到导航应被视为副作用的意见,但再次1.4。
  4. Toast - is there something in state or we consider this as side effect? 吐司 - 有什么州或我们认为这是副作用? Same problems. 同样的问题。

Google suggests SingleLiveEvent solution, but it looks weird and then there should be as much LiveData<SingleLiveEvent> streams as we have such things, not really a single source of truth. Google建议使用SingleLiveEvent解决方案,但它看起来很奇怪,然后应该有尽可能多的LiveData<SingleLiveEvent>流,因为我们有这样的东西,而不是真正的单一事实来源。 Others suggest new intent generated from render func which is better but there is a possibility that some async operations will change state again and we'll get second Toast while first is showing and so on. 其他人建议使用render func生成的新意图更好,但有些异步操作可能会再次改变状态,我们将在第一次显示时获得第二个Toast,依此类推。

1) Notes App: In a perfect world: yes your ViewState would have an text changes whenever the user inserts the text and renders. 1)Notes应用程序:在一个完美的世界中:是的,只要用户插入文本并呈现,您的ViewState就会有text更改。 Regarding the recursion: I might be wrong but I think that RxBindings somewhere offers an Observable that not only contains the changed text, but also a boolean flag if this change has been caused by user input or by programmatically setting the text. 关于递归:我可能错了,但我认为RxBindings提供了一个Observable,它不仅包含已更改的文本,而且还包含一个布尔标志,如果此更改是由用户输入或通过编程设置文本引起的。 Anyways, I think you could also workaround the recursion if you check if (editText.text != viewState.text) and only set the text in case that they are different (keep in mind that you may have to use the TextWatcher callback that is triggered afterward text has been changed to start the intent, not "before is going to be changed"). 无论如何,我认为如果你检查if (editText.text != viewState.text)你也可以解决递归问题,并且只在它们不同的情况下设置文本(请记住,你可能必须使用TextWatcher回调,触发后续文本已更改为启动意图,而不是“之前将要更改”)。

With that said, on Android we are not living in a perfect world. 话虽如此,在Android上我们并不是生活在一个完美的世界。 As you have already said, the text will be restore automatically by android. 正如您已经说过的,文本将由android自动恢复。 Therefore it makes sense to not make text part of the ViewState. 因此,不将文本作为ViewState的一部分是有意义的。

So it sounds that in that case the ViewState is just an enum like this: 所以听起来在这种情况下,ViewState只是一个这样的枚举:

enum ViewState {
   // The user can type typing text
   IDLING,

   // The app is saving the note
   PROCESSING,

   // After having saved (PROCESSING) the note, CLEARED means, show a new empty note  
   CLEARED
}

So the initial state is IDLING . 所以最初的状态是IDLING Then once the note should be saved the next emitted ViewState is PROCESSING . 然后,一旦保存了音符,下一个发出的ViewState就是PROCESSING Once this was successful, your business logic immediately fires a CLEARED followed immediately by IDLING so at the end the user sees an empty note again and can start typing the new note. 一旦成功,您的业务逻辑会立即触发CLEARED然后立即触发IDLING以便最后用户再次看到一个空注释并开始键入新注释。

Don't use doOnNext() for manipulating the view. 不要使用doOnNext()来操作视图。 ViewState is the single source of truth for the view. ViewState是视图的唯一真实来源。

Regarding RecyclerView: RecyclerView restores it's scroll position automatically, if not (you set the LayoutManager and / or adapter to late, after state has been restored). 关于RecyclerView:RecyclerView会自动恢复它的滚动位置,否则(在将状态恢复后,将LayoutManager和/或适配器设置为迟到)。 Nevertheless, if you would like to model the scroll position in ViewState, which again in a perfect world would be the best solution I guess, you should consider not update the scroll position in your ViewState on each pixel scrolled but rather do it once the user is not scrolling anymore / fling has finished. 不过,如果你想在ViewState中对滚动位置进行建模,那么在完美的世界中,我认为这是最好的解决方案,你应该考虑不在每个滚动的像素上更新ViewState中的滚动位置,而是在用户做一次不再滚动/ fling已经完成。

2) Github app: 2)Github app:

  1. We should show progressBar and disable okButton while loading. 我们应该在加载时显示progressBar并禁用okButton。 We should have like loading/content/error sealed class(lets call it ContentState) and have its instance in our ViewState. 我们应该喜欢加载/内容/错误密封类(让我们称之为ContentState)并将其实例放在我们的ViewState中。 View knows how to render ContentState.loading and shows progressBar and disables okButton. View知道如何呈现ContentState.loading并显示progressBar并禁用okButton。 Am I right? 我对吗?

Yes

  1. How to handle navigation then? 如何处理导航呢?

For me, handling this as a side effect works well: I have a class Navigator that is injected into the presenter and used in doOnNext { navigator.goToX() } . 对我来说,处理这个副作用效果很好:我有一个类Navigator ,它被注入到演示者并在doOnNext { navigator.goToX() } The Navigator then dispatches that to another component that can be temporarily attached / detached. 然后导航器将其分派给另一个可以临时连接/分离的组件。 So this other component is observing the Navigator for "navigation events" The reason why I would do that is that then this component is not leaking activity / fragment context. 所以这个其他组件正在观察导航器中的“导航事件”我之所以这样做,那么这个组件就不会泄漏活动/片段上下文。 "This component" can be directly the Activity or Fragment or whatever, but I tend to have a dedicated class, let's call it Router that observes the Navigator for navigation events and does the FragmentTransactions or whatever you use in your app. “这个组件”可以直接是Activity或Fragment或者其他什么,但是我倾向于有一个专用的类,让我们称之为Router ,它观察Navigator的导航事件,并执行FragmentTransactions或你在应用程序中使用的任何内容。

  1. Toast - is there something in state or we consider this as side effect? 吐司 - 有什么州或我们认为这是副作用? Same problems. 同样的问题。

This can be handled similar to what you can do with Snackbar (see here ). 这可以像使用Snackbar (见这里 )。 Toast doesn't have an API to hide a Toast. Toast没有隐藏Toast的API。 So instead of a timer you can immediately fire two ViewState one after another: the first one with the error flag set (which then causes the Toast do be displayed on screen) and the second one where you "clear" this flag. 因此,您可以立即触发两个ViewState而不是计时器:第一个设置了错误标志(然后导致Toast显示在屏幕上),第二个设置“清除”此标志。 Something like this: 像这样的东西:

Observable.just( ViewState(error = true, ...), new ViewState( error = false, ... )

I hope that clarifies some things, but as always: Don't take them as a silverbullet. 我希望澄清一些事情,但一如既往:不要把它们当成银子弹。 Do whatever works best for your app and use case. 做任何最适合您的应用和用例的事情。 Don't be super religious, it's always a case by case decision. 不要超级宗教,这总是一个案例决定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM