Главный вопрос, который нам здесь нужно решить – а где будет жить экземпляр Router?
Если он будет жить и использоваться только во View, то никаких проблем нет – у нас есть напрямую доступ к Activity.
Сложней, если ссылка на Router нужна внутри Presenter. В таком случае вам не обойтись без явной передачи "чего-то" из View в Presenter. Здесь вы встаёте перед другим выбором: что передать из View? Context? А если Router будет не стартовать Activity, а менять фрагменты? Тогда придётся передавать что-то другое. Таким образом само собой напрашивается решение из соседней статьи: из View вы устанавливаете в Presenter непосредственно экземпляр Router, с которым в будущем будете работать из Presenter.
Мне кажется, если реализовывать VIPER, то нужно идти по второму пути и просто в onCreate передавать в Presenter экземпляр Router. В таком случае хотелось бы обратить внимание на две вещи:
Не забудьте убирать Router из Presenter, когда View уничтожается(иначе будет утечка памяти)
Вы можете расширить функционал MvpDelegate, добавив в метод `onCreate` указывание Router для Presenter, и очищая ссылку на Router в Presenter внутри метода MvpDelegate `onDestroy`
PS: Router ломается, если вы начинаете строить приложение не на фрагментах, а на custom view, т.к. при смене конфигурации вы потеряете все изменения лэйаута. В таком случае не используйте Router, а работайте прямыми командами во View из Presenter через ViewState. Тогда вы не потеряете ваши изменения после изменения конфигурации.
В большинстве случаев, здесь будет достаточно сделать так, чтоб на каждую страницу ViewPager был свой Presenter. Так вам будет проще всего – не нужно будет ничего разруливать.
В таком случае, каждый Fragment будет по-своему инициализировать свой Presenter, а Presenter будет уже доставать нужные данные. И вам будет очень просто обработать команды из Presenter, и оба фрагмента будут независимы друг от друга.
Если вам нужно, чтоб команда отрабатывала исключительно один раз, значит она не должна быть сохранена во ViewState. Для этого у неё должна быть стратегия SkipStrategy. Её можно указать, применив к методу start в интерфейсе View аннотацию: @StateStrategyType(SkipStrategy.class)
Ещё на заметку, ваш код можно изменить:
в activity, в методе onCreate выполните ваш код presenter.setStartValue(getIntent().getExtras().getInt(Constants.VALUE));
в presenter, в методе setStartValue сохарните пришедшее значение где-нибудь в presenter
в методе onFirstViewAttach берёте это значение и работаете с ним
Но это не обязательно – ваш подход абсолютно так же будет работать. Просто имейте ввиду возможность такого способа =)
Учтите, что метод onFirstViewAttach будет вызван только при первом привязывании view. А после поворота девайса, он уже не будет вызван. Но похоже вы это и так поняли =)
Если брать именно Moxy, то вам ничего не придётся переписывать при выпиливании либы. Придётся только дописывать =) Я вас не уговариваю, а просто информирую ;)
в каждом методе View сохранять в Bundle какое-то описание состояния
складывать этот Bundle в outState
в onCreate передавать этот Bundle в Presenter
в Presenter смотреть в метод onFistViewAttached, есть ли Bundle
если есть Bundle, «парсить» его и давать команды во ViewState
У этого способа есть минус – он не автоматизирован. Но есть и плюс – лишний раз Bundle парситься не будет. А вы как-нибудь автоматизировали создание сериализуемого ViewState?
Библиотека за вас архитектуру не построит — только поможет автоматизировать рутинную работу(по типу сохранения стейте). Но, конечно – не хотите — не используйте
Хотелось бы рассказать как мы решили этот вопрос в Moxy (т.к. мы решили что это главная проблема, которая стоит перед нами) — View аттачится в onStart(только если до этого был onCreate), а детачится в onDestroy. Так и утечек памяти нет, и лишний раз не применяются команды ко View из ViewState. В то же время, т.к. ViewState храниться в Presenter, а не во View, он не должен быть сериализуемым :)
Да, мы очень хотели, чтоб пришлось писать минимум кода. И в то же время хотелось попробовать annotation processor =) Результат крайне порадовал – для полноценного сохранения состояния достаточно применить аннотацию @GenerateViewState к MvpView и @InjectViewState к MvpPresenter. Когда видишь этот код и результат его работы, кажется что там есть магия =)
Правда, если можно обойтись без кодогенерации/рефлексии, используя только наследование/композицию, это наверное даже круче.
Понятно, а мы решили, что раз процесс убился, и всё-равно потерялись все Presenter, то просто пусть заново будет создан Presenter и всё начнётся сначала. Я замечал, что у стоковых Android-приложений именно такое поведение =)
Да, у нас тоже легко сделать кейс что на другой активити такого же типа будет использоваться другой Presenter =) Вообще, изначально все Presenter – локальные. И, соответственно, на каждый экран свои Presenter. А вот если указать глобальный тэг, то будет использоваться везде один Presenter. Ну и спец. фишка – динамический тэг для глобального презентера. Например, открыли список своих контактов → создался Presenter для нашего списка контактов. Затем открыли список контактов друга → создался Presenter для списка его контактов. Затем вернулись к своему списку контактов, и тут уже не создаётся новый Presenter, а берётся старый. Актуально может быть, например, если эти Presenter очень долго отрабатывают и будет обидно потерять их.
А ваше решение где-нибудь опубликовано? Было бы интересно посмотреть =)
У вас видимо ViewState хранится во View, поэтому вы вынуждены сериализовывать его и складывать в Bundle?
Мы решили развязать пользователю руки, и поэтому ссылка на ViewState хранится в Presenter. Presenter в свою очередь хранится не в Activity, а в статичном хранилище. Это позволяет не зависеть Presenter(а значит и ViewState) от жизненного цикла View. И поэтому даже если команда во ViewState прилетела в то время, когда View не приаттачена к Presenter/к ViewState, как только View будет приаттачена, ViewState сообщит ей весь набор команд, которые она должна выполнить. За счёт этого можно из Presenter передавать в командах даже несериализуемые данные.
А если вы это и говорили, то круто, что мы не одни так подумали =)
И да, у нас идёт тэгирование не View, а Presenter ;)
Moxy довольно легко расширяется до VIPE®. Правда, Router там особо не нужен, но может и он войдёт.
Разница между статьями в том, что эта статья – про архитектуру. А статья про Moxy – больше про то, как используя Moxy построить приложение, подходящее под паттерны MVP & Co. Так что эта статья вам очень пригодится ;)
Мало того, решение можно сделать ещё более «простым», подпилив gson, чтоб не нужно было писать аннотации в модели =) Gson умеет «сам» убирать префикс m и переводить CamelCase к lower_case_with_underscores и обратно(когда переводим объект в json):
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setFieldNamingStrategy(new FieldNamingStrategy {
public String translateName(Field field) {
String name = FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES.translateName(field);
name = name.substring(2, name.length()).toLowerCase();
return name;
}
})
.create();
А я ничего не констатировал — что они хранят в открытом виде. Но сам факт, что высылают на почту мне мой пароль в открытом виде, не нравится. Никогда не любил, когда так делают. Кроме случаев, когда пароль генерируется при регистрации.
В моей памяти не помню, чтоб 100500 сервисов так делали. Помню 1 точно, и ещё один, не точно. Итого, теперь их в списке 3. А вы, видимо, считаете это хорошей практикой?
Не понимаю, о чём статья-то? И для чего она здесь? В ней совершен какой-то front-end прорыв? Или как-то особенно используются какие-то новые интересные инструменты? Сарказма нет. Может я действительно что-то не понимаю?
Если он будет жить и использоваться только во View, то никаких проблем нет – у нас есть напрямую доступ к Activity.
Сложней, если ссылка на Router нужна внутри Presenter. В таком случае вам не обойтись без явной передачи "чего-то" из View в Presenter. Здесь вы встаёте перед другим выбором: что передать из View? Context? А если Router будет не стартовать Activity, а менять фрагменты? Тогда придётся передавать что-то другое. Таким образом само собой напрашивается решение из соседней статьи: из View вы устанавливаете в Presenter непосредственно экземпляр Router, с которым в будущем будете работать из Presenter.
Мне кажется, если реализовывать VIPER, то нужно идти по второму пути и просто в onCreate передавать в Presenter экземпляр Router. В таком случае хотелось бы обратить внимание на две вещи:
PS: Router ломается, если вы начинаете строить приложение не на фрагментах, а на custom view, т.к. при смене конфигурации вы потеряете все изменения лэйаута. В таком случае не используйте Router, а работайте прямыми командами во View из Presenter через ViewState. Тогда вы не потеряете ваши изменения после изменения конфигурации.
В таком случае, каждый Fragment будет по-своему инициализировать свой Presenter, а Presenter будет уже доставать нужные данные. И вам будет очень просто обработать команды из Presenter, и оба фрагмента будут независимы друг от друга.
start
в интерфейсе View аннотацию: @StateStrategyType(SkipStrategy.class)Ещё на заметку, ваш код можно изменить:
onCreate
выполните ваш кодpresenter.setStartValue(getIntent().getExtras().getInt(Constants.VALUE));
setStartValue
сохарните пришедшее значение где-нибудь в presenteronFirstViewAttach
берёте это значение и работаете с нимНо это не обязательно – ваш подход абсолютно так же будет работать. Просто имейте ввиду возможность такого способа =)
Учтите, что метод
onFirstViewAttach
будет вызван только при первом привязывании view. А после поворота девайса, он уже не будет вызван. Но похоже вы это и так поняли =)У этого способа есть минус – он не автоматизирован. Но есть и плюс – лишний раз Bundle парситься не будет. А вы как-нибудь автоматизировали создание сериализуемого ViewState?
Правда, если можно обойтись без кодогенерации/рефлексии, используя только наследование/композицию, это наверное даже круче.
Да, у нас тоже легко сделать кейс что на другой активити такого же типа будет использоваться другой Presenter =) Вообще, изначально все Presenter – локальные. И, соответственно, на каждый экран свои Presenter. А вот если указать глобальный тэг, то будет использоваться везде один Presenter. Ну и спец. фишка – динамический тэг для глобального презентера. Например, открыли список своих контактов → создался Presenter для нашего списка контактов. Затем открыли список контактов друга → создался Presenter для списка его контактов. Затем вернулись к своему списку контактов, и тут уже не создаётся новый Presenter, а берётся старый. Актуально может быть, например, если эти Presenter очень долго отрабатывают и будет обидно потерять их.
А ваше решение где-нибудь опубликовано? Было бы интересно посмотреть =)
Мы решили развязать пользователю руки, и поэтому ссылка на ViewState хранится в Presenter. Presenter в свою очередь хранится не в Activity, а в статичном хранилище. Это позволяет не зависеть Presenter(а значит и ViewState) от жизненного цикла View. И поэтому даже если команда во ViewState прилетела в то время, когда View не приаттачена к Presenter/к ViewState, как только View будет приаттачена, ViewState сообщит ей весь набор команд, которые она должна выполнить. За счёт этого можно из Presenter передавать в командах даже несериализуемые данные.
А если вы это и говорили, то круто, что мы не одни так подумали =)
И да, у нас идёт тэгирование не View, а Presenter ;)
Разница между статьями в том, что эта статья – про архитектуру. А статья про Moxy – больше про то, как используя Moxy построить приложение, подходящее под паттерны MVP & Co. Так что эта статья вам очень пригодится ;)
В моей памяти не помню, чтоб 100500 сервисов так делали. Помню 1 точно, и ещё один, не точно. Итого, теперь их в списке 3. А вы, видимо, считаете это хорошей практикой?