MVP в Android'е со сложным интерфейсом из нескольких фрагментов. Как лучше скоординировать?

Всем привет.

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

Предположим, что у нас есть two-pane приложение - для конкретности возьмем карту с маркерами и список этих маркеров. Соответственно имеются два фрагмента: MyMapFragment и MyListFragment. В общем случае события выделения, добавления, удаления, нажатия и прочие могут отлавливаться любым из них. Задача в том, что требуется координировать состояния обоих отображений: если мы выделили маркер на карте, то нужно выделить его и в списке, или если изменили название маркера в списке, то нужно тут же обновить его на карте.
Вопрос: какой способ наиболее корректен?

Я вижу три варианта:
1) Все вопросы координации вьюшек лежит на Presenter'е.
b1b8f57065f14d119bdc194f36e2fc39.png
Соответственно сразу после инстанцирования Presenter'а, мы регистрируем каждый фрагмент в нем как реализацию интерфейсов для колбэков MarkersView. При этом важный момент: Presenter явно знает какая реализация к чему относится и какая именно реализация к нему обращается. Иными словами, помимо функции маршрутизации данных от View к Model и обратно, наш Presenter теперь так же занимается маршрутизацией нотификаций о событиях между известными ему View.
Но как я понимаю, это нарушает идеологию MVP в том плане, что Presenter теперь слишком многое знает о представлениях.

2) Presenter занимается только маршрутизацией данных, а согласованиями вьюшек - отдельный класс-посредник MarkersViewsMediator.
e5b5a9de7d0445da83250d52553da8ad.png
В этом случае Presenter слеп и принимает события от любого из фрагментов и вызывает колбэки того фрагмента, который к нему обратился. Для согласования своих состояний фрагменты используют MarkersViewMediator в качестве общей шины для передачи обновлений своих состояний.
Но тут можно заметить потенциальную проблему - Presenter ничего не знает о зарегистрированных в нем фрагментах (это хорошо), поэтому может либо дёрнуть коллбэк одного обратившегося к нему фрагмента (похоже на вариант, когда у каждого фрагмента своя копия presenter'а), либо всех сразу (и тут уже могут всплыть проблемы и нехороший код).

3) Как п.2, но Fragment'ы общаются с Presenter'ом только через посредника.
32197528868a4561b22e5213f401b46f.png
Иными словами, наш посредник становится мастер-представлением, координирующим работу фрагментов, отсылающим события Presenter'у и предоставляющим реализацию колбэков.
Но размеры такого класса могут быть внушительны и в целом он должен полностью знать подключенные к нему фрагменты.
Можно заметить, что на роль такого посредника может подойти активити, содержащая эти самые фрагменты. Правда придётся тесно завязываться на события её жизненного цикла. Другой вариант - отдельный синглтон.

Или может есть другой более правильный способ?
  • Вопрос задан
  • 4755 просмотров
Пригласить эксперта
Ответы на вопрос 1
@KonstantinKiriushyn
Android Dev
По best practices взаимодействие двух фрагментов организовывается через Activity, которое их хостит.
Среди предложенных вариантов мне больше всего мне нравится вариант 3.
В рамках задачи я бы сделал Activity = MasterView, и общался бы Presenter'ом c Activity. Далее Activity служило бы Mediator'om и неким координатором для фрагментов.

НО.

Предлагаю рассмотреть схему с двумя Presenter'ами. Первый отвечает за загрузку списка маркером и работает с MyListFragment, второй за работу с картой и привязан к MyMapFragment.
А медиатором все так же является Activity с интерфейсами.

2f22433a5e2840f5b78bb17638a9acb0.png

Как это будет работать? (внешний код опущен)

MyListFragment:
//MapInterface реализует Activity для взаимодействия между фрагментами.
mMapInterface.onMarkerSelected(marker);


Activity:
public void onMarkerSelected(Marker marker) {
      mMapFragment.selectMarker(marker);
}


MapFragment:
public void selectMarker(Marker marker) {
       //например грузим информацию по маркеру
      mMapPresenter.loadMarkerInfo(marker, someCallback).
      //или просто производим манипуляции со View.
}


Также:
Если же операции, выполняемые с Моделью одинаковые, то можно обойтись один презентером, но все равно он будет дергаться из Фрагментов. Потому что, раз мы говорим о хорошем дизайне, в случае дальнейшего усложенения логики Фрагментов, лучше чтоб каждый хостил свой Презентер.

С удовольствием подискутирую на тему.
Спасибо!
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы