Отдельные временные события в архитектуре MVI
Попытка новой парадигмы архитектуры, когда презентатор создает поток неизменного состояния (модель), а представление просто отображает его.
Не могу понять, как обращаться с ситуациями, когда нам нужно сделать какое-то событие только один раз. Есть пара примеров.
1) Заметки приложение. У нас есть editText
а также saveButton
, Клики пользователей saveButton
происходит некоторая обработка, и editText должен быть очищен. Не могли бы вы, ребята, опишите, что будет в нашем ViewState
вот и приблизительный логический поток?
Вопросы и подводные камни, которые я вижу сейчас:
- Мы подписываемся на
editText.textChanges()
в ведущий. Если мы будем иметьtext
в нашемViewState
и рендерим его при каждом вызове рендеринга, тогда мы впадаем в рекурсию, потому что он выдаст новый textChange и обновит состояние и отрендерит снова. - Нужны ли нам
text
вViewState
чтобы восстановить его по тексту ориентации или уничтожить / восстановить процесс, похоже, что он работает из коробки здесь. Но представьrecyclerView
положение прокрутки. Нам определенно нужно сохранить его для восстановления. Мы не можем восстановить его при каждом вызове рендеринга, потому что это выглядит странно, не так ли? - Если мы рассмотрим такую логику как побочный эффект и назовем
.doOnNext{ view.clearText() }
это имеет смысл, но есть ли у нас ссылка на представление в канонической реализации MVI? У Мосби этого нет, как я вижу. - Это имеет смысл, но есть вероятность, что в момент
doOnNext
вызов. МВИ должен помочь нам в этом, но только если это частьViewState
, право?
2) Приложение Github. Первый экран (Org): orgEditText
, okButton
, progressBar
, Второй экран (Repos): recyclerView
, Когда пользователь входит в организацию orgEditText
и щелкает okButton
приложение должно сделать запрос к API и в случае успеха перейти к экрану Repos в случае успеха или показать тост в случае неудачи. Опять не могли бы вы описать ViewState
для экрана орг и как должна выглядеть логика?
Вопросы и подводные камни, которые я вижу сейчас:
- Мы должны показать
progressBar
и отключитьokButton
во время загрузки. У нас должен быть запечатанный класс loading / content / error (давайте назовем егоContentState
) и иметь свой экземпляр в нашемViewState
, View умеет рендеритьContentState.loading
и показываетprogressBar
и отключаетokButton
, Я прав? - Как обращаться с навигацией тогда? Те же вопросы, что и 1.3 и 1.4.
- Я видел мнения, что навигация должна рассматриваться как побочный эффект, но опять же 1.4.
- Тост - есть ли что-то в состоянии или мы рассматриваем это как побочный эффект? Те же проблемы
Google предлагает SingleLiveEvent
решение, но выглядит странно, и тогда должно быть столько же LiveData<SingleLiveEvent>
потоки, как у нас есть такие вещи, на самом деле не единственный источник правды. Другие предлагают новое намерение, сгенерированное из функции рендеринга, что лучше, но есть вероятность, что некоторые асинхронные операции снова изменят состояние, и мы получим второй Toast, пока показывается первый, и так далее.
1 ответ
1) Приложение Notes: в идеальном мире: да, ваш 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 приложение:
- Мы должны показать progressBar и отключить okButton во время загрузки. У нас должен быть запечатанный класс loading/content/error (назовем его ContentState) и иметь его экземпляр в нашем ViewState. View знает, как визуализировать ContentState.loading, показывает progressBar и отключает okButton. Я прав?
да
- Как обращаться с навигацией тогда?
Для меня обработка этого как побочный эффект работает хорошо: у меня есть класс Navigator
который вводится в докладчик и используется в doOnNext { navigator.goToX() }
, Затем Navigator отправляет это другому компоненту, который может быть временно присоединен / отсоединен. Так что этот другой компонент наблюдает за навигатором "навигационные события". Причина, по которой я бы сделал это, заключается в том, что тогда этот компонент не пропускает контекст активности / фрагмента. "Этот компонент" может быть непосредственно Деятельностью или Фрагментом или чем-то еще, но у меня, как правило, есть выделенный класс, давайте назовем его Router
что соблюдает Navigator
для навигационных событий и делает FragmentTransactions
или что вы используете в своем приложении.
- Тост - есть ли что-то в состоянии или мы рассматриваем это как побочный эффект? Те же проблемы
Это можно сделать аналогично тому, что вы можете сделать с Snackbar
(см. здесь). Тост не имеет API, чтобы скрыть тост. Таким образом, вместо таймера вы можете сразу запустить два ViewState один за другим: первый с установленным флагом ошибки (который затем заставляет Toast отображаться на экране), а второй, где вы "очищаете" этот флаг. Что-то вроде этого:
Observable.just( ViewState(error = true, ...), new ViewState( error = false, ... )
Я надеюсь, что это проясняет некоторые вещи, но как всегда: не принимайте их как серебряную пулю. Делайте все, что лучше всего подходит для вашего приложения и варианта использования. Не будь супер религиозным, это всегда индивидуальное решение.