【问题标题】:Single time events in MVI architectureMVI 架构中的单次事件
【发布时间】:2017-12-12 16:50:47
【问题描述】:

尝试新的架构范式,其中演示者创建不可变状态(模型)流,而视图只呈现它。

无法理解如何处理我们只需要制作一次事件的情况。有几个例子。

1) 笔记应用。我们有editText 和saveButton。用户点击saveButton,会发生一些处理并清除editText。各位大佬能否在此处描述一下我们的ViewState 中的内容以及大致的逻辑流程?

我现在看到的问题和陷阱:

  1. 我们在presenter中订阅editText.textChanges()。如果我们在 ViewState 中有 text 并在每次渲染调用时渲染它,那么我们将陷入递归,因为它会发出新的 textChange 并会更新状态并再次渲染。
  2. 我们是否需要text 中的ViewState 来在方向文本或进程终止/恢复上恢复它,看起来它在这里开箱即用。但是想象一下recyclerViews 的滚动位置。我们绝对需要保存它才能恢复。我们无法在每次渲染调用时恢复它,因为它看起来很奇怪,不是吗?
  3. 如果我们将这样的逻辑视为副作用并调用.doOnNext{ view.clearText() } 是有道理的,但是我们在规范的 MVI 实现中是否参考了视图?正如我所见,莫斯比没有它。
  4. 这是有道理的,但在doOnNext 调用的那一刻,视图可能会死掉。 MVI 应该帮助我们解决这个问题,但前提是它是 ViewState 的一部分,对吧?

2) Github 应用。第一个屏幕(组织):orgEditText、okButton、progressBar。第二屏(回购):recyclerView。当用户进入orgEditText 组织并单击okButton 时,应用程序应向 API 发出请求,并在成功时导航到 Repos 屏幕(成功时)或显示 toast(失败时)。您能否再次为 Org 屏幕描述 ViewState 以及应该是什么样的逻辑?

我现在看到的问题和陷阱:

  1. 我们应该在加载时显示progressBar 并禁用okButton。我们应该有像加载/内容/错误密封类(让我们称之为ContentState)并将它的实例放在我们的ViewState中。 View 知道如何渲染ContentState.loading 并显示progressBar 并禁用okButton。我说的对吗?
  2. 那么如何处理导航呢?与 1.3 和 1.4 相同的问题。
  3. 我看到了导航应该被视为副作用的意见,但还是 1.4。
  4. Toast - 状态是否存在或我们认为这是副作用?同样的问题。

Google 建议 SingleLiveEvent 解决方案,但它看起来很奇怪,然后应该有尽可能多的 LiveData<SingleLiveEvent> 流,因为我们拥有这样的东西,而不是真正的单一事实来源。 其他人建议从渲染函数生成的新意图更好,但有可能一些异步操作会再次改变状态,我们将在第一个显示时获得第二个 Toast,依此类推。

【问题讨论】:

  • 也许this 会有所帮助
  • 这没有帮助,它只是小吃店的一种解决方法,而不是我正在寻找的一些通用解决方案。

标签: android architecture kotlin mosby


【解决方案1】:

1) 笔记应用: 在一个完美的世界中:是的,每当用户插入文本并呈现时,您的 ViewState 都会发生 text 更改。关于递归:我可能错了,但我认为某处的 RxBindings 提供了一个 Observable,它不仅包含更改的文本,而且如果此更改是由用户输入或以编程方式设置文本引起的,则还包含一个布尔标志。无论如何,我认为如果您检查 if (editText.text != viewState.text) 并且仅在它们不同的情况下设置文本,我认为您也可以解决递归问题(请记住,您可能必须使用在文本已更改为开始之后触发的 TextWatcher 回调意图,而不是“之前将要改变”)。

话虽如此,在 Android 上,我们并不是生活在一个完美的世界中。正如您已经说过的,文本将由 android 自动恢复。因此,不将文本作为 ViewState 的一部分是有意义的。

所以听起来在这种情况下 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
}

所以初始状态是IDLING。然后,一旦应该保存注释,下一个发出的 ViewState 就是PROCESSING。成功后,您的业务逻辑会立即触发CLEARED,然后立即触发IDLING,因此最后用户会再次看到一个空便笺,并可以开始输入新便笺。

不要使用doOnNext() 来操作视图。 ViewState 是视图的唯一真实来源。

关于 RecyclerView:如果没有,RecyclerView 会自动恢复它的滚动位置(在状态恢复后,您将 LayoutManager 和/或适配器设置为延迟)。不过,如果您想在 ViewState 中对滚动位置进行建模,这在完美的世界中将是我猜想的最佳解决方案,您应该考虑不要在每个滚动像素上更新 ViewState 中的滚动位置,而是在用户滚动后执行不再滚动/投掷已完成。

2) Github 应用:

  1. 我们应该在加载时显示progressBar并禁用okButton。我们 应该有像加载/内容/错误密封类(让我们称之为 ContentState) 并在我们的 ViewState 中有它的实例。视图知道如何 渲染 ContentState.loading 并显示 progressBar 并禁用 确定按钮。我说的对吗?

是的

  1. 那么如何处理导航呢?

对我来说,将其作为副作用处理效果很好:我有一个类 Navigator 注入到演示者中并在 doOnNext { navigator.goToX() } 中使用。然后,导航器将其分派给可以临时附加/分离的另一个组件。所以这个其他组件正在观察“导航事件”的导航器我这样做的原因是这个组件没有泄漏活动/片段上下文。 “这个组件”可以直接是 Activity 或 Fragment 或其他任何东西,但我倾向于有一个专门的类,我们称之为Router,它观察Navigator 的导航事件并执行FragmentTransactions 或任何你在你的应用程序。

  1. Toast - 状态是否存在或我们认为这是副作用?同样的问题。

这可以像使用Snackbar 一样处理(请参阅here)。 Toast 没有隐藏 Toast 的 API。因此,您可以一个接一个地立即触发两个 ViewState,而不是计时器:第一个设置了错误标志(然后导致 Toast 确实显示在屏幕上),第二个您“清除”此标志。像这样的:

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

我希望澄清一些事情,但一如既往:不要把它们当作灵丹妙药。做最适合您的应用程序和用例的事情。不要过于虔诚,这始终是个案决定。

【讨论】:

  • 感谢您的回答。对于这些情况,这种双重状态一个接一个地发出看起来像是一个很好的通用解决方案。你认为应该怎么做,我们的演示者扫描只发出一个 ViewState,我们需要两个连续的 Actions/PartialStates 之类的东西,第一个开始做某事标志,第二个清除这个标志,对吗?但是这些发射之间仍然可能存在差距,可能会发生其他一些状态变化,不是吗?
  • 理论上是的,但是状态缩减器(scan() 操作员)会注意最终您会得到正确的视图状态。
  • 我的意思是最后没关系,是的,但是如果我们的状态中的某些“showError”布尔标志显示 Toast 等于 true,并且另一个动作将在清除动作之前导致新状态减少然后将显示两个 Toast,这显然看起来不正确。
  • zour scan() 获取“showError 操作”,输出是带有 showError = true 的 ViewState。下一个“另一个动作”出现(在处理“清除错误动作”之前):在这种情况下,scan() 运算符知道 ViewState.showError == true 并将其设置为 false。这可以防止第二个吐司出现在屏幕上。接下来,“清除错误操作”由 scan() 运算符处理,并且由于 showError 已设置为 false,因此输出仍然是带有 showError == false 的 ViewState。这就是状态归约器的美妙之处:它确保您获得正确的状态作为输出,而不管输入发生的顺序如何
  • @sockeqwe 为什么当 state reducer 知道它需要清除错误标志时,你甚至需要“清除错误操作”?
猜你喜欢
  • 2020-07-10
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 2019-07-24
  • 1970-01-01
  • 2023-03-31
  • 2011-10-19
  • 1970-01-01
相关资源
最近更新 更多