Pull to refresh

Comments 42

Поставьте пожалуйста тег Kotlin. Я уж чуть было не подумал, что промахнулся статьей, глядя на листинги с незнакомым синтаксисом.
Насколько я понял, это все-таки просто MVVM, в котором ViewModel называется PresentationModel, а в качестве механизма связывания данных используется Rx.

MVVM — это модификация более общего паттерна Presentation Model, со специфической реализацией автоматического связывания данных. Причем датабиндинг зависит от конкретной UI-платформы. В случае с RxPM мы имеем полуавтоматический биндинг: приходится вручную подписываться на Observable и отписываться от него. Стоит различать форму связывания, поэтому не совсем корректно называть представленный паттерн как RxMVVM.

Вручную или автоматически, это субъективно. В том же WPF и прочих XAML само ничего не происходит, во View обязательно должно быть указано, к каким частям ViewModel она привязана, иначе это уже какая-то магия. В вашей реализации связывание происходит в onBindPresentationModel. Возможно, оно выглядит непривычно, т.к. описано не внутри разметки. Но где ему быть, это детали конкретной реализации, а суть в точности та же.


Если совсем абстрагироваться от деталей и посмотреть еще раз, то в понимании MVVM Fragment — это слой View, и в нем таким же образом, как в XAML или XML (на примере Android Data Binding Library) описаны привязки. Я вижу MVVM.

Полностью согласен, ведь в данном случае binding это и есть Rx, мнение о том что MVVM в андроиде без data binding library не может существовать ошибочно.

Не совсем. Если почитать о MVVM, то становится ясно, что data binding — это один из столпов MVVM. А согласно описанию data binding, он должен сам связывать вьюшку с вьюмоделью, в то время как с Rx это приходится делать самому.
Более того, давайте обратимся к истокам. Однажды Джон Госсман (человек, который и придумал паттерн MVVM) написал статью в которой рассказывал и о PresentationModel. По его же словам В PresentationModel раздражала ручная синхронизация вьюшки и модели. Его хотелось минимизировать. В результате чего в WPF и придумали data binding.
Так что автор все правильно написал. Ведь, грубо говоря, если из MVVM убрать data binding, то получим PresentationModel.

Не совсем. Если почитать о MVVM, то становится ясно, что data binding — это один из столпов MVVM. А согласно описанию data binding, он должен сам связывать вьюшку с вьюмоделью, в то время как с Rx это приходится делать самому.

Может, data binding сам должен обновлять View? Связывание само не происходит, в том или ином виде оно всегда описано. Содержание остается тем же, а несущественная разница в форме не делает его "ручным" или "автоматическим".


Так что автор все правильно написал. Ведь, грубо говоря, если из MVVM убрать data binding, то получим PresentationModel.

Верно, и автор как раз добавил data binding в форме Rx.

Не путайте людей, пожалуйста.
Прочтите статьи по ссылкам. В описании паттерна Presentation Model все очень доступно описано.


Суть отличия не просто в наличии databinding'а. Он есть и там и там. Data binding в прямую переводится как связывание данных. Без этого все паттерны были бы бесполезны.
Отличие — в наличии автоматического датабиндинга.
В случае с PM его не было. И позже, в WPF его добавили (позже). То есть MVVM развился из PM
Поэтому называть PM как MVVM это как называть мотоцикл автомобилем.

Я как раз никого не путаю, а напротив, пытаюсь развеять заблуждения.
Статьи я прочел (почему вы решили иначе?).


И позже, в WPF его добавили (позже).

Неверно, data binding в WPF присутствовал с момента первого релиза (WPF 3.0).


Поэтому называть PM как MVVM это как называть мотоцикл автомобилем.

Согласен. Но называть автомобиль мотоциклом я также не буду.


Суть отличия не просто в наличии databinding'а. Он есть и там и там. Data binding в прямую переводится как связывание данных. Без этого все паттерны были бы бесполезны.
Отличие — в наличии автоматического датабиндинга.

Того, на чем вы так сильно ставите акцент, нет ни по одной ссылке. Что для вас автоматическое, а что нет?


Думаю, вы не там ищете разницу.


Было бы очевидно, что все делается вручную, если бы все ивенты по старинке обрабатывались вручную, и в каждом обработчике в императивном стиле изменялись бы зависимые свойства View или PM. Такое никак нельзя было бы назвать MVVM.
Здесь же, благодаря Rx, присутствует "автоматическое" оповещение об изменениях свойств, и все связи описаны декларативно в том самом onBindPresentationModel подобно тому, как это делается в разметке в других реализациях.


Приведу цитату из статьи по второй ссылке. Совсем короткая, но в ней собрано всё, чтобы понять суть:


If the binding has the correct settings and the data provides the proper notifications, then, when the data changes its value, the elements that are bound to the data reflect changes automatically.

Все это присутствует у автора:


  • data provides the proper notifications = Observable типы в PM
  • if the binding has the correct settings = onBindPresentationModel написан без ошибок
  • when the data changes its value, the elements that are bound to the data reflect changes automatically = после "активации" связей путем однократного выполнения onBindPresentationModel элементы View автоматически меняются вслед за изменением VMPM, и наоборот (например, для полей ввода)

А обычный listener оповещает не автоматически?
Тогда PM вообще не существует как паттерн и есть только MVVM.
Ну что ж, ок, только не говорите об этом Фаулеру, не расстраивайте человека ;)

Попытка съязвить не усилит вашу аргументацию.


А обычный listener оповещает не автоматически?

Оповещает, конечно. Просто он некрасив.
Обратимся к Фаулеру, который, как вы думаете, на вашей стороне, за определением:


Data Binding
A mechanism that ensures that any change made to the data in a UI control is automatically carried over to the underlying session state (and vice versa).

Как видите, здесь лишь общие слова, и нигде не сказано, какую форму должен принимать data binding — {Binding ...}, source.subscribe(target) или же уродливый классический listener.


Зато в определении сказано, что смысл Data Binding в том, что изменения сразу передаются из UI в состояние и наоборот. То есть изменяются синхронно, а состояние двух частей приложения, соответственно, синхронизировано, в чем и заключается основной профит привязывания данных.


Таким образом, даже простой listener, который занимается синхронизацией двух значений, формально полностью подходит под это определение. Да даже без Фаулера сложно спорить с тем, что если вы заставили синхронно изменяться данные, то вы их связали.


И кстати, из этого определения также следует, что автор статьи ошибочно противопоставляет binding при помощи Rx (частную реализацию) data binding'у в целом (общему понятию).


Data Binding, помимо названия отдельных технологий, еще и абстракция. И в данной статье она применена.

Вы пишете так много, что уже сами теряете нить.


Вопрос был о том, является ли связывание при помощи listener'а автоматическим.
Вы же ответили


Оповещает, конечно.

и зачем-то расписали свой ответ подробнее.


Суть в том, что есть 2 паттерна. Не один. Два. И мы сравнивая их видим отличие только в отношении связывания: "автоматическое" ли оно.


В нашем понимании в MVVM да, тк там фреймворк позволяет тебе не писать бойлерплейт код, а в PM нет, тк его приходится писать.


Связывание есть и там и там. Поэтому обсуждать databinding не вижу смысла.
Если хотите продолжать обсуждение, давайте вернемся к вопросу о понимании слова "автоматически".


Я выше написал, что понимаю под этим, а что вкладываете в это понятие вы?

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


Может, data binding сам должен обновлять View? Связывание само не происходит, в том или ином виде оно всегда описано. Содержание остается тем же, а несущественная разница в форме не делает его "ручным" или "автоматическим".

То есть даже если за вас методы, инициирующие связывание, вызывает парсер разметки, вы все равно обязаны указать ему, что с чем связывать. Не укажете ничего — ничего и не свяжется "само".


Другими словами, я утверждаю, что связывание автоматически не происходит нигде. Автоматически изменяются значения зависимых свойств благодаря связыванию.


И это не просто мое мнение. Что Фаулер, что Microsoft в своих материалах применяют слово автоматический в ином контексте: при изменении некого значения другое должно автоматически меняться вслед за ним. И все, вот так банально. А data binding — это механизм, благодаря которому такое автоматическое изменение происходит. А те реализации, которые предоставляют android/winforms/wpf/angular/whatever, просто делают его кратким/удобным для использования (а иногда наоборот).


Причем, если смотреть на то, как красиво оно описано/количество boilerplate-кода, то с этим в настоящей статье все хорошо. Человек, знакомый с Rx, с легкостью опознает в строке


pm.loadingState.subscribe(progressBar.visibility())

привязку одного свойства к другому. Вам нужно еще более "автоматически"?


А вот вы на мой вопрос "Что для вас автоматическое, а что нет?" не ответили.


Я выше написал, что понимаю под этим

Либо я плохо смотрю. Хотя несколько раз просмотрел ваши комментарии, но ответа не увидел. Не могли бы вы написать еще раз?

Мы тут спорим или общаемся?


"Описал выше" это прямо в том же комменте. Неужели не заметно было?
Вот:


Суть в том, что есть 2 паттерна. Не один. Два. И мы сравнивая их видим отличие только в отношении связывания: "автоматическое" ли оно.
В нашем понимании в MVVM да, тк там фреймворк позволяет тебе не писать бойлерплейт код, а в PM нет, тк его приходится писать.

Ответил? Тогда жду ответ на вопрос:


является ли связывание при помощи listener'а автоматическим.

Из ваших комментов получается, что связывание автоматическое всегда. И нигде не происходит автоматически. Тем самым вы отрицаете само существование паттерна PresentationModel и при этом еще и приводите Фаулера в доводы.


Вам нужно еще более "автоматически"?

Да, в Databinding Library и других это происходит еще более автоматически.


Теперь вернемся к Фаулеру:


Probably the most annoying part of Presentation Model is the synchronization between Presentation Model and view. It's simple code to write, but I always like to minimize this kind of boring repetitive code. Ideally some kind of framework could handle this, which I'm hoping will happen some day with technologies like .NET's data binding.

В статье же Джона Гроссмана, которую указал Mujahit, тот продолжает


WPF has done exactly that by including a very rich powerful data-binding engine. Basically, in the past all of these frameworks worked using the Observer or Publish/Subscribe pattern. In the simplest version the Model publishes change notifications and the View subscribes and updates itself in response to events. Not only is this code repetitive, but it can be a source of bugs and perf problems. The data binding engine in Avalon just automates all that work, and provides 2-way binding to boot that minimizes how much work you have to do to push changes from the View back into the Model.

Никаких знакомых слов не заметили? Выделю:


The data binding engine in Avalon just automates all that work

Еще немножечко сокращу:


automates all that work

Итак:


Мы понимаем различие между MVVM и PM как его понимают создатели этих паттернов.
В наличии автоматического databinding'a. Чего-то, что позволяеет упростить написание кода для связывания.


Вы же не станете отрицать, что написать в XML поле от которого зависит виджет проще, чем писать то, что приходится в приведенном нами паттерне?
Я отвечу — конечно проще.
Ведь, код, необходимый для связывания появится автоматически.


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

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


Автоматическое связывание в вашем понимании означает, что нет бойлерплейта? Все верно? Если да, то у меня для вас новость. В андроиде нужно либо оборачивать каждое поле в ObservableField<T>, либо сам класс модели должен наследовать интерфейс Observable, а в сеттерах должен вызываться метод, уведомляющий об изменениях. Аналогично и в WPF, вы используете либо DependencyProperty на каждое свойство, либо интерфейс INotifyPropertyChanged (и его родственников), превращая авто-свойства из одной строки в простыни однотипного текста. Всё перечисленное — boilerplate.


Уже одно это разбивает вашу предпосылку (та, что в цитате 1) в щепки.


Далее про "автоматическое Шрёдингера".
Вы называете автоматическим связыванием отсутствие boilerplate кода.
Автоматическое, значит без участия человека, совсем [1]. В отличии от автоматизированного, где требуется участие человека. Поэтому я и утверждаю, что его нет. Ведь во всех MVVM-фреймворках приходится писать что с чем связать, даже если мало. А как мы только что выяснили, еще и boilerplate нужен, так что его дважды нет.
А что происходит автоматически, так это изменение одних значений сразу после изменений других [2].
Так вот, [1] и [2] — это две разные вещи. Я догадывался по прошлым коментариям, что вы их не различаете, но теперь точно знаю.


Теперь про знакомые слова. Вы не видите смысла за отдельными словами. ".NET's data binding" — название конкретной технологии, а не databinding как самостоятельное понятие. "The data binding engine in Avalon" — оно же применительно конкретно к Avalon (WPF), т.е. еще более узко.
Здесь написано вовсе не "data binding делает работу автоматической". Здесь написано "вот это конкретная реализация от Microsoft автоматизирует всю работу". А вы почему-то решили из отдельных слов составить тот смысл, который вам будет удобен.
Обратите внимание еще и вот на что:


  • Автоматизирует. Здесь говорится именно об этом. Автоматизированный это не автоматический.
  • Прочтите выше про boilerplate в моделях, и поймете, что они погорячились насчет "all"

Ведь, код, необходимый для связывания появится автоматически

Неверно, не весь, см. про boilerplate.


Про краткость (в XML) не стану отрицать. Но где граница между "достаточно кратко" и "еще слишком многословно"? Кто должен это решать, и почему он? То, что эта граница субъективна по своей природе — еще одна причина, по которой "краткость записи" не может служить критерием для разделения между PM и MVVM.


Может, вообще не стоит различать PM и MVVM, а считать их разными именами одной сущности. Некоторые авторы так и делают. (То, что я пишу это, не значит, что ваши ошибки, на которые я указал выше, исчезли. Если вы над ними подумаете, это не сделает вас глупее или проигравшим).


В таком случае называть эту архитектуру MVVM также некорректно, как и говорить, что это PM.


Если вас устроит такой вывод, предлагаю завершить разговор. (Продолжить можно, но я устал от него)


Комментарий снова получился гигантским. Если я где-то в нем задел вас, прошу прощения, это неумышленно.

Я тоже догадывался о том, что вы говорите в основном о том, что где-то некорректно было применено слово автоматически. И я согласен, что корректнее будет использовать слово автоматизированно.


Про бойлерплейт, вы опять к словам цепляетесь. Никто не говорил про полное отсутствие. Его просто меньше.


Просто ведь если какой-то фреймворк "автоматизирует" работу, то значит какая-то часть работы происходит "автоматически". И тут начинается все заново. Поэтому пусть разбираются филологи и любители относительности. А спор на эту тему тут надо завершать.


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


А вот этот выпад не очень корректен:


То, что я пишу это, не значит, что ваши ошибки, на которые я указал выше, исчезли. Если вы над ними подумаете, это не сделает вас глупее или проигравшим.
У вас ошибок было тоже полно. Так что говорить такое некрасиво.

Но "без обид".


Всего хорошего! Разговор завершен.

> Мы понимаем различие между MVVM и PM как его понимают создатели этих паттернов.

> WPF has done exactly that by including

> WPF has done

> WPF

wait…

Простите, но фреймворк WPF — это не паттерн MVVM.
Паттерн и фреймворк — вообще вещи ортоганальные.

Паттерн через фреймворк определять вообще нельзя.А то окажется, что каждый, кто написал хелпер придумал новый паттерн.

Согласен. Это не MVVM. Но речь и не шла о том, что это MVVM. Слова вырваны из контекста.

Суть в том, что есть 2 паттерна. Не один. Два. И мы сравнивая их видим отличие только в отношении связывания: "автоматическое" ли оно.

В нашем понимании в MVVM да, тк там фреймворк позволяет тебе не писать бойлерплейт код, а в PM нет, тк его приходится писать.

Я у вас вижу только контекст, где вы отличие паттернов видите в наличии фреймворка.


Ничем иным объяснить зачем вы слова о WPF притащили а текст между жалобами на PM Фаулера и «Мы понимаем различие между MVVM и PM» у меня не получается.


Можете привести пример реализации автоматического биндинга в MVVM без фреймворка, и неавтоматического в PM, что бы разница в походах была понятна?

Если вы реализуете автоматический(автоматизированный) биндинг, то получится по-сути фреймворк. Поэтому в вашем вопросе нет смысла. Тк из всего, что было написано ранее видно, что MVVM без автоматизированного датабиндинга это и есть PM.
Но видимо вы не читаете текст, а просто ищите поводы поспорить.

Да, из всего написанного вами следует, что MVVM без автоматизированного датабиндинга это и есть PM.

Проблема только в том, что вы этот вывод сделали сами, выдернув «автоматический» из описания WPF, а MVVM и его создатели тут ни при чем.

blogs.msdn.microsoft.com/johngossman/2005/10/11/the-presentationmodel-pattern

Да? А по ссылочке перейти и прочитать:


Then use Avalon's data binding functionality to declaratively (in the XAML) wire the View to the ViewState and/or directly to the Model.

На Википедию тоже не будет лишним сослаться (https://ru.wikipedia.org/wiki/Model-View-ViewModel):


Первоначально был представлен сообществу Джоном Госсманом (John Gossman) в 2005 году как модификация шаблона Presentation Model

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

То есть вы «декларативный» от «автоматического» тоже не отличаете, как и Avalon от MVVM? Ну ок.

> На Википедию тоже не будет лишним сослаться

Да, только не на нашу же
en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel

Даже моя ссылка противоречит написанному в нашей.

А есть ли гитхаб репозиторий с исходниками этого приложения?

Чуть позже выложу

кстати, хороший пример использования подобного подхода как для Android, так и для iOS – приложения Kickstarter, они открыли исходники не так давно:



примеры особенны интересны тем, что это большое приложение целиком

Классная статья. Расскажите пожалуйста, как с этим подходом отображать ошибки, например, с помощью алерт-диалога. Чем в этом случае будет ошибка — стейтом или эвентом? Как правильно обработать скрытие ошибки пользователем?

Все ошибки можно разделить на два типа:
1) Ошибки, которые нужно показать один раз, например AlertDialog или Toast. В этом случае ошибка будет эвентом, ее сохранять не нужно. Для этого подойдет обычный PublishRelay. Но будет проблема, если ошибка прилетит в тот момент, когда вьюха отсоединена от PresentationModel. В этом случае мы потеряем этот эвент. Как решать эту проблему я расскажу в следующей статье.


2) Ошибки, которые нужно показывать как заглушку в разметке, например с кнопкой "Retry", такой вариант нужно считать стейтом. Для этого нужно использовать BehaviorRelay.

Можно узнать, почему данный доклад не пропустили на Mobius?

Судя по всему, тк он мог "конфликтовать" с докладом Степана Гончарова и Дениса Неклюдова.
Хотя по-моему это не так. В их подходе используется обычный MVVM c Databinding Library, да и доклад был про подход к архитектуре в приложении в целом, а не только о презентационном паттерне. Поэтому, я думаю, конфликта не было бы. Но, что сделано, то сделано. Зато статья вышла быстрее ;)

Несколько вопросов/комментариев:
  • Соответственно, ссылка на интерфейс View в презентере будет обнулена. Поэтому нужно всегда делать проверку на null, когда требуется обновить View.
    Ссылку на интерфейс не обязательно обнулять. Можно подсунуть пустую реализацию интерфейса, тогда проверку делать не надо. В этом случае view будет val/@NonNull. Но даже если ссылка на вью будет nullable, при использовании котлина всегда можно заюзать Safe Call оператор ?.
  • На сколько данный подход поддается тестированию?
  • Зачем вам во фрагменте вот эта строчка
    retainInstance = true
    

    Тут и память может потечь, и в бэкстэк этот фрагмент не положить
  1. Не все пишут на Котлине, можно делать интерфейс-заглушку, но ее тоже придется генерировать. Но проблема не в этом. Так как вьюха может быт отсоединена, то приходится сохранять стейт в презентере в виде флагов, чтобы потом его воспроизвести при атаче вью.
  2. По поводу тестирования RxJava, есть хороший доклад на эту тему: https://www.youtube.com/watch?v=7W5NwpE5WpQ&feature=youtu.be
  3. Ретейн фрагмент только для семпла, так то для прода они не годятся. Как вы уже заметили в бекстеке такие фрагменты нельзя использовать и есть баги с чайлд-фрагментами. Я в своих приложениях использую Conductor — это такие "правильные" фрагменты, которые не умирают в бекстеке и при поворотах. А насчет памяти тут все в порядке, на onDestroyView мы отписываемся от PresentationModel.
Интересно. Пользуюсь похожим подходом последние года 1.5, основные проблемы возникали именно с правильным хранением/изменением состояния и с навигацией.

Насчет состояния — для экранов храню его в saveState, но, по-хорошему, его нельзя изменять после onSaveInstanceState, так что надо отписываться в правильных местах, грубо говоря.

Насчет навигации, если делать на фрагментах/вьюхах — тоже нельзя ей пользоваться после onSaveInstanceState, иначе ее состояние не сохранится. Если на активити, то связать между собой 2 активити будет проблематично, если одна активити меняет состояние PM другой активити.

В двух словах не ответите, как примерно решаете такие проблемы?

Основная идея паттерна PM заключается в том, что стейт хранится в PresentationModel. View не нужно об этом беспокоиться и не нужно складывать стейт в Bundle. Главное реализовать хранение PresentationModel во время поворота.


По поводу навигации, нужно складывать команды в буфер, и воспроизводить их когда навигатор (активити) будет готов. Посмотрите как это сделано в Cicerone.

Спасибо за интересную статью! dmdev а как подразумевается обработка ситуации, когда процесс убился и произошло последующее восстановление Activity?

В этом случае у Вас PresentationModel будет в состоянии A а View в состоянии B

Точно так же как и в Moxy ;)


Рестарт процесса вещь неприятная, но не всегда требуется при этом восстанавливать View в то же самое состояние. Так как данные за время отсутствия пользователя в приложении могли устареть. Все зависит от конкретного приложения и в каждом случае нужно то или иное решение:


1) Точно восстановится бэкстек из активити и фрагментов. При желании этот момент можно отследить и очистить бекстек, если приложение стартовало с восстановлением.


2) Самые важные параметры экранов (параметры запуска) мы стараемся передавать через Intent или аргументы фрагмента, например id сущностей, которые нужно отобразить. PresentationModel получает их в конструкторе, так как View провайдит ее.


3) Не все состояния нужно восстанавливать. Например прогресс загрузки не нужно восстанавливать, так как с убийством процесса все асинхронные запросы (в том числе и в сеть) тоже завершатся.


4) Есть данные, которые быстро устаревают, например какие-нибудь статусы заказа. Лучше будет их заново запросить с сервера.


5) Некоторые данные следует восстанавливать даже после принудительного завершения приложения. В этом случае никакие bundle нам не помогут. Например это может быть корзина с продуктами. Такие данные во время работы приложения нужно сохранять на диск (в бд или файл).


6) Хорошо кешировать данные, которые не сильно теряют актуальность за относительно продолжительное время.


7) Можно запустить сервис, чтобы повысить приоритет приложения в фоне. Тем самым снизить вероятность убийства процесса системой.


8) В конце концов в PresentationModel можно пробрасывать вызовы сохранения/восстановления состояния из bundle. Но этот вариант не подходит для персистентных данных (пункт 5 и 6).

Sign up to leave a comment.