[英]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
. 我们有
editText
和saveButton
。 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: 我现在看到的问题和陷阱:
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. ViewState
有text
并为每个渲染调用渲染它,那么我们将进入递归,因为它将发出新的textChange并将更新状态并再次渲染。 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. .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. doOnNext
call. doOnNext
调用的那一刻有可能看到死亡。 MVI should help us with this but only if it's the part of ViewState
, right? ViewState
的一部分,对吧? 2) Github app. 2)Github应用程序。 First screen(Org):
orgEditText
, okButton
, progressBar
. 第一个屏幕(组织):
orgEditText
, okButton
, progressBar
。 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: 我现在看到的问题和陷阱:
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
. ContentState.loading
并显示progressBar
并禁用okButton
。 Am I right? 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:
- 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 是
- 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
或你在应用程序中使用的任何内容。
- 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.