Одноразовые события в архитектуре MVI

Попытка новой парадигмы архитектуры, когда ведущий создает поток неизменного состояния (модели), и просмотр просто отображает его.

Не могу понять, как обращаться с ситуациями, когда нам нужно сделать какое-то событие только за один раз. Есть несколько примеров.

1) Примечания к приложению. У нас есть editText и saveButton . Пользователь нажимает saveButton , происходит некоторая обработка, и editText следует очистить. Не могли бы вы, ребята, описать, что будет в нашем ViewState и приблизительный логический поток?

Вопросы и подводные камни, которые я вижу сейчас:

  1. Мы подписываемся на editText.textChanges() в презентаторе. Если мы будем иметь text в нашем ViewState и визуализируем его каждый вызов рендеринга, то мы попадаем в рекурсию, потому что он будет генерировать новый textChange и будет обновлять состояние и визуализировать снова.
  2. Нужен ли нам text в ViewState чтобы восстановить его на текст ориентации или уничтожить / восстановить процесс, выглядит так, как будто это работает вне коробки. Но представьте себе позицию прокрутки recyclerView . Нам обязательно нужно сохранить его для восстановления. Мы не можем восстановить его при каждом вызове рендеринга, потому что это выглядит странно, не так ли?
  3. Если мы рассмотрим такую ​​логику как побочный эффект и вызов .doOnNext{ view.clearText() } это имеет смысл, но имеем ли мы ссылку на представление в канонической реализации MVI? У Мосби это не так, как я вижу.
  4. Это имеет смысл, но есть возможность того, что вид мертв в момент вызова doOnNext . MVI должен помочь нам в этом, но только если это часть ViewState , не так ли?

2) Приложение Github. Первый экран (Org): orgEditText , okButton , progressBar . Второй экран (Repos): recyclerView . Когда пользователь вводит организацию в orgEditText и нажимает okButton приложение должно подать запрос на API и при успешном orgEditText перейти к экрану Repos при успехе или показать тост при ошибке. Опять же не могли бы вы охарактеризовать экран ViewState для Org и какая логика должна выглядеть?

Вопросы и подводные камни, которые я вижу сейчас:

  1. Мы должны показывать progressBar и отключать okButton во время загрузки. У нас должен быть класс загрузки / содержимого / ошибки, который был закрыт (позволяет называть его ContentState ) и иметь свой экземпляр в нашей ViewState . View знает, как отображать ContentState.loading и показывает progressBar и отключает okButton . Я прав?
  2. Как управлять навигацией? Те же вопросы, что и 1.3 и 1.4.
  3. Я видел мнения о том, что навигацию следует рассматривать как побочный эффект, но снова 1.4.
  4. Тост – есть что-то в состоянии или мы считаем это побочным эффектом? Те же проблемы.

Google предлагает решение SingleLiveEvent , но выглядит странно, и тогда должно существовать столько LiveData<SingleLiveEvent> сколько у нас есть такие вещи, а не один источник истины. Другие предлагают новое намерение, полученное из функции funf, что лучше, но есть вероятность, что некоторые операции async снова изменят состояние, и мы получим второй тост, пока он показывается и так далее.

    1) Приложение для заметок: в идеальном мире: да, ваш ViewState будет иметь text изменения всякий раз, когда пользователь вставляет текст и визуализирует. Что касается рекурсии: я могу ошибаться, но я думаю, что RxBindings где-то предлагает Observable, который содержит не только измененный текст, но и логический флаг, если это изменение было вызвано пользовательским вводом или программной установкой текста. В любом случае, я думаю, вы могли бы также обходной рекурсии, если вы проверили if (editText.text != viewState.text) и только установили текст в случае, если они разные (помните, что вам, возможно, придется использовать обратный вызов TextWatcher, который вызванный текст после этого был изменен, чтобы начать намерение, а не «раньше будет изменено»).

    С учетом сказанного, на 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 . Как только это будет успешным, ваша бизнес-логика сразу же IDLING а затем сразу же IDLING поэтому в конце пользователь снова увидит пустую ноту и может начать вводить новую заметку.

    Не используйте doOnNext() для управления представлением. ViewState – единственный источник истины для представления.

    Что касается RecyclerView: RecyclerView автоматически восстанавливает свою позицию прокрутки, если нет (вы устанавливаете LayoutManager и / или адаптер в конце, после восстановления состояния). Тем не менее, если вы хотите смоделировать позицию прокрутки в ViewState, которая снова в идеальном мире будет лучшим решением, я думаю, вам следует подумать о том, чтобы не обновлять позицию прокрутки в вашем представлении в каждом прокручиваемом пикселе, а делать это как только пользователь больше не прокручивается / fling закончил.

    2) Приложение Github:

    1. Мы должны показывать progressBar и отключать okButton во время загрузки. У нас должен быть класс загрузки / содержимого / ошибки, который был закрыт (позволяет называть его ContentState) и иметь свой экземпляр в нашей ViewState. View знает, как отображать ContentState.loading и показывает progressBar и отключает okButton. Я прав?

    да

    1. Как управлять навигацией?

    Для меня обработка этого как побочного эффекта хорошо работает: у меня есть класс Navigator который вводится в презентатор и используется в doOnNext { navigator.goToX() } . Затем Navigator отправляет это другому компоненту, который может быть временно присоединен / отсоединен. Таким образом, этот другой компонент наблюдает навигатор для «событий навигации». Причина, по которой я буду делать это, заключается в том, что тогда этот компонент не протекает в контексте активности / фрагмента. «Этот компонент» может быть непосредственно Activity или Fragment или что-то еще, но я имею тенденцию иметь выделенный класс, назовем его Router который наблюдает за навигационными событиями Navigator и делает FragmentTransactions или что-то, что вы используете в своем приложении.

    1. Тост – есть что-то в состоянии или мы считаем это побочным эффектом? Те же проблемы.

    Это можно обрабатывать аналогично тому, что вы можете сделать с помощью Snackbar (см. Здесь ). У Тоста нет API, чтобы скрыть Тост. Поэтому вместо таймера вы можете сразу запустить два ViewState один за другим: первый с установленным флажком ошибки (который затем вызывает тост, который будет отображаться на экране), а второй, где вы «очистите» этот флаг. Что-то вроде этого:

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

    Надеюсь, что это прояснит некоторые вещи, но, как всегда: не воспринимайте их как серебряный букет. Сделайте все, что лучше всего подходит для вашего приложения и использования. Не будьте суперрелигиозными, это всегда случайное решение.