Как обращаться с навигацией на основе пользовательского интерфейса в приложениях Cross Platform? - программирование

Как обращаться с навигацией на основе пользовательского интерфейса в приложениях Cross Platform?

Предположим, что у вас есть кросс-платформенное приложение. Приложение работает на Android и iOS. Ваш общий язык на обеих платформах - Java. Как правило, вы должны написать свою бизнес-логику в Java и всю свою специфическую часть интерфейса в Java (для Android) и Objective-C (для iOS).

Обычно, когда вы реализуете шаблон MVP в кросс-платформе, кросс-языковое приложение, вы должны иметь модель и презентатор на Java и предоставлять Java-интерфейс для ваших представлений, который известен вашим докладчикам. Таким образом, ваши общие Java-презентаторы могут взаимодействовать с любой реализацией вида, которую вы используете в определенной части платформы.

Предположим, мы хотим написать приложение iOS с Java-частью, которую позже можно будет использовать с тем же Android-приложением. Вот графическое представление дизайна:

enter image description here

С левой стороны находится Java-часть. В Java вы пишете свои модели, контроллеры, а также интерфейсы представления. Вы делаете все проводки, используя инъекцию зависимостей. Затем код Java можно перевести на Objective-C с помощью J2objc.

С правой стороны у вас есть часть Objective-C. Здесь ваш UIViewController может реализовать интерфейсы Java, которые, если они переведены в протоколы ObjectiveC.

Проблема:

То, что я боюсь, - это то, как происходит переключение между представлениями. Предположим, что вы находитесь на UIViewControllerA, и вы нажимаете кнопку, которая должна привести вас к UIViewControllerB. Что бы вы сделали?

Случай 1:

enter image description here

Вы сообщаете о нажатии кнопки на Java ControllerA (1) UIViewControllerA, а Java ControllerA вызывает Java ControllerB (2), который связан с UIViewControllerB (3). Тогда у вас есть проблема, с которой вы не знаете со стороны контроллера Java, как вставить UIViewControllerB в иерархию вида Objective-C. Вы не можете справиться с этим со стороны Java, потому что у вас есть только доступ к интерфейсам View.

Случай 2:

enter image description here

Вы можете сделать переход на UIViewControllerB независимо от того, является ли он модальным или с помощью UINavigationController или что-то еще (1). Затем сначала нужен правильный экземпляр UIViewControllerB, который привязан к Java ControllerB (2). В противном случае UIViewControllerB не смог бы взаимодействовать с Java ControllerB (2,3). Когда у вас есть правильный экземпляр, вам нужно сказать Java ControllerB, что был обнаружен View (UIViewControllerB).

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

Как смоделировать навигацию между различными контроллерами и обрабатывать кросс-платформу. Соответственно просматривать изменения?

4b9b3361

Ответ 1

Я бы порекомендовал вам использовать какой-то механизм слотов. Как и другие среды MVP.

Определение. Слот является частью представления, в которое могут быть вставлены другие представления.

В своем презентаторе вы можете определить столько слотов, сколько хотите:

GenericSlot slot1 = new GenericSlot();
GenericSlot slot2 = new GenericSlot();
GenericSlot slot3 = new GenericSlot();

Эти слоты должны иметь ссылку в представлении Presenter. Вы можете реализовать

setInSlot(Object slot, View v);

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

Посмотрите, как реализованы слоты здесь.

Ответ 2

Краткий ответ:

Вот как мы это делаем:

  • Для простых "нормальных" вещей (например, кнопка, которая открывает камеру устройства или открывает еще один Activity/UIViewController без какой-либо логики действия) - ActivityA открывается непосредственно ActivityB. ActivityB теперь отвечает за взаимодействие с общим логическим уровнем приложения при необходимости.
  • Для чего-то более сложного или логически зависимого мы используем 2 варианта:
    • ActivityA вызывает метод некоторого UseCase, который возвращает enum или public static final int и принимает некоторые действия соответственно -OR -
    • UseCase может вызывать метод ScreenHandler, который мы зарегистрировали ранее, который знает, как открыть общий Activities из любого места приложения с некоторыми предоставленными параметрами.

Длинный ответ:

Я ведущий разработчик в компании, использующей java-библиотеку для моделей приложений, логики и бизнес-правил, которые реализуются как мобильными платформами (Android, так и iOS) с помощью j2objc.

Мои принципы дизайна приходят непосредственно от дяди Боба и SOLID, мне очень не нравится использование MVP или MVC при проектировании целых приложений в комплекте с межкомпонентной связью, потому что тогда вы начинаете связывать каждый Activity с 1 и только 1 Controller иногда это нормально, но в большинстве случаев вы получаете объект God контроллера, который имеет тенденцию меняться так же, как View. Это может привести к серьезным запахам кода.

Мой любимый способ (и тот, который я нахожу самым чистым) в обработке этого, разбивает все на UseCases, каждый из которых обрабатывает 1 "ситуацию" в приложении. Уверен, у вас может быть Controller, который обрабатывает несколько из этих UseCases, но тогда все, что он знает, это то, как делегировать те UseCases и ничего больше.

Кроме того, я не вижу причин связывать каждое действие Activity с Controller сидящим на логическом уровне, если это действие является простым "забрать меня на экран карты" или что-нибудь в этом Сортировать. Роль Activity должна обрабатывать Views, который она хранит, и как единственная "умная" вещь, живущая в жизненном цикле приложения, я не вижу причин, по которым она не может назвать начало следующей деятельности,

Кроме того, жизненный цикл Activity/UIViewController слишком сложный и слишком отличается друг от друга, чтобы обрабатываться общей java lib. Это то, что я рассматриваю как "деталь", а не "бизнес-правила", каждая платформа должна реализовывать и беспокоиться, тем самым делая код в java lib более прочным и не подверженным изменениям.

Опять же, моя цель состоит в том, чтобы каждый компонент приложения был как SRP (принцип единой ответственности), каким он может быть, а это означает как можно больше сочетать как можно больше вещей.

Итак, пример простого "нормального" материала:

(все примеры являются полностью мнимыми)

ActivityAllUsers отображает список элементов объекта модели. Эти элементы пришли из вызова AllUsersInteractor - a UseCase controller в обратном потоке (который, в свою очередь, также обрабатывается java lib с отправкой в ​​основной поток, когда запрос завершен). Пользователь нажимает на один из элементов в этом списке. В этом примере ActivityAllUsers уже имеет объект модели, поэтому открытие ActivityUserDetail является прямым вызовом с пакетом (или другим механизмом) этого объекта модели данных. Новое действие ActivityUserDetail отвечает за создание и использование правильного UseCases, если необходимы дальнейшие действия.

Пример сложного логического вызова:

ActivityUserDetail имеет кнопку под названием "Добавить как друг", которая при нажатии вызывает метод обратного вызова onAddFriendClicked в ActivityUserDetail:

public void onAddFriendClicked() {  
  AddUserFriendInteractor addUserFriend = new AddUserFriendInteractor();
  int result = addUserFriend.add(this.user);
  switch(result){
    case AddUserFriendInteractor.ADDED:
      start some animation or whatever
      break;
    case AddUserFriendInteractor.REMOVED:
      start some animation2 or whatever
      break;
    case AddUserFriendInteractor.ERROR:
      show a toast to the user
      break;
    case AddUserFriendInteractor.LOGIN_REQUIRED:
      start the log in screen with callback to here again
      break;

  }
}

Пример еще более сложного вызова

A BroadcastReceiver на Android или AppDelegate на iOS получите push-уведомление. Это отправляется в NotificationHandler, который находится в логическом слое java lib. В конструкторе NotificationHandler, который строится один раз в App.onCreate(), он принимает ScreenHandler interface, который вы реализовали на обеих платформах. Это push-уведомление анализируется, и правильный метод вызывается в ScreenHandler, чтобы открыть правильный Activity.

В нижней строке: держите View настолько тупым, насколько сможете, держите Activity достаточно умным, чтобы обрабатывать свой собственный жизненный цикл и обрабатывать свои собственные представления и общаться со своим собственным controllers (множественное число!), а все остальное должно быть написано (надеюсь, test-first;)) в java lib.

Используя эти методы, наше приложение в настоящее время работает около 60-70% своего кода в java lib, а следующее обновление должно довести его до 70-80%.

Ответ 3

В межплатформенном развитии, использующем то, что я называю "ядром" (домен вашего приложения, написанным на Java), я склонен предоставить владельцу UI, представление которого будет отображаться дальше. Это делает ваше приложение более гибким, адаптируясь к окружающей среде по мере необходимости (используя UINavigationController на iOS, фрагменты на Android и одну страницу с динамическим контентом в веб-интерфейсе).

Ваш controllers не должен привязываться к представлению, а вместо этого выполнять определенную роль (accountController для входа/выхода из системы, recipeController для отображения и редактирования рецепта и т.д.).

У вас interfaces для controllers вместо views. Затем вы можете использовать Factory шаблон дизайна, чтобы создать экземпляр controllers на стороне домена (ваш код Java) и views на стороне пользовательского интерфейса. view factory имеет ссылку на ваш домен controller factory и использует его для предоставления запрашиваемому представлению некоторых контроллеров, реализующих определенные интерфейсы.

enter image description here

Пример: После нажатия кнопки "Войти", homeViewController запрашивает ViewControllerFactory для loginViewController. Этот factory в свою очередь запрашивает ControllerFactory для контроллера, реализующего интерфейс accountHandling. Затем он создает новый loginViewController, дает ему только что полученный контроллер и возвращает этот свежеприведенный контроллер представления в homeViewController. Затем homeViewController представляет новый пользовательский контроллер представления.

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

Вы можете взглянуть на этот упрощенный демонстрационный проект, который я сделал, который иллюстрирует эту настройку (минус интерфейсы).