Мы рассмотрели выходные потоки (ViewState и ActionState) в части 1.

Краткий обзор выходных потоков в части 1:

  • MVVM использует шаблон наблюдателя, в котором модель представления является субъектом, а представление - наблюдателем.
  • В представлении может быть несколько наблюдателей, поэтому я предложил а) ViewState Observer для обновлений пользовательского интерфейса, таких как установка текста TextView, и б) ActionState Observer для таких событий, как «выход из всех устройств».


В этой части я опускаю ActionState Observer, поскольку одного ViewState Observer достаточно для демонстрации «хорошей практики», которую я хочу здесь представить.

Соревнование

Теперь после инициализации есть важные события с важными данными, которые необходимо обработать, например onActivityResult, «Проведите для обновления». Их получает только View (Activity / Fragment), поэтому предполагается, что они будут ретранслироваться в ViewModel.

Как?

В конечном итоге мы что-то делаем (вы не одиноки, это было в моем предыдущем приложении)

Обзор

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

Пример побочных эффектов

Модель просмотра

в котором

  • someUsefulVariable является изменяемым и влияет на конечный результат основной логики ViewModel. (В идеальном мире, где мы можем создать идеальную ViewModel без сохранения состояния, этого не будет. На самом деле, есть много случаев этого в тысячах ViewModels, с которыми я работал за последние 2,5 года в двух разных технических единорогах, и я твердо верю, что они никогда не перестанут существовать)
  • Основная логика ViewModel doStep1()...doStepK()...handleResult { finalResult -> может быть сложной и состоять из многих шагов, и для завершения требуется некоторое время.
  • Синтаксис здесь вроде .handleResult { finalResult -> является воображаемым. Это может быть .subscribe { от RxJava или .collect { от Kotlin Flow.

Как видите, конечный результат основной логики может быть непреднамеренно изменен, когда View вмешивается в инициализацию поста состояния ViewModel.

Что еще хуже, состояние гонки может привести к изменению конечного результата, что делает отладку очень сложной.

Схема выглядит примерно так:

Разве это не похоже на Model-View-Presenter (MVP)?

Просмотр - ›ViewModel -› Просмотр - Однонаправленные данные

Что ж, как уже говорилось в части 1, данные передаются в одном направлении от объекта ViewModel к наблюдателю представления (шаблон наблюдателя). Почему бы не добавить еще один поток для обеспечения ввода, например нажатия кнопок (из представления в модель представления)?

Для этой цели я использовал PublishSubject или BehaviouralSubject RxJava (в зависимости от того, нужно ли мне выдавать значение по умолчанию или нет). Наверняка в Kotlin Flow есть что-то подобное. (Это каналы вроде BroadcastChannel?)

Обзор

обратите внимание, что inputSubjectReadOnly доступен только для чтения, поэтому предотвращается обратный побочный эффект, то есть ViewModel не может изменять входные данные из View!

Модель просмотра

Предварительное условие: как вы можете это сделать, Input Stream нельзя использовать без среды реактивного программирования, такой как Kotlin Flow или RxJava.

Теперь стало легче читать и отлаживать нашу ViewModel. Данные, кажется, идут только в одну сторону

Кинжал опасность

Если вы предоставляете свои входные потоки через Dagger через что-то вроде

При первом запуске экрана создается тема щелчка 1 (PublishSubject), а ViewModel создается с помощью темы щелчка 1.

Во время вращения ViewModel отсоединяется от представления, а затем снова присоединяется к представлению после поворота. Однако после поворота создается новый объект щелчка 2 и получает события щелчка от представления. Наша ViewModel все еще ожидает событий щелчка по теме щелчка 1, которая уже заменена субъектом щелчка 2.

Другими словами, ваша логика кликов будет нарушена после ротации.

Средство

Свяжите тему щелчка и ViewModel вместе, используя определенную область