На этой схеме не скелет древнего обитателя водных глубин и не схема метро какого-то мегаполиса, это карта переходов по экранам вполне реального Андроид приложения! Но, несмотря на сложность, нам удалось её удачно реализовать, а решение оформить в виде небольшой библиотеки, о которой и пойдет речь в статье.
Чтобы заранее избежать вопросов о названии, уточню: Cicerone ("чи-че-ро?-не") – устаревшее слово с итальянскими корнями, со значением «гид для иностранцев».
В наших проектах мы стараемся придерживаться архитектурных подходов, которые позволяют отделить логику от отображения.
Так как я в этом плане предпочитаю MVP, то далее по тексту будет часто встречаться слово «презентер», но хочу отметить, что представленное решение никак не ограничивает вас в выборе архитектуры (можно даже использовать в классическом подходе «все во Fragment’ах», и даже в этом случае Cicerone даст свой профит!).
Навигация – это скорее бизнес-логика, поэтому ответственность за переходы я предпочитаю возлагать на презентер. Но в Андроиде не все так гладко: для осуществления переходов между Activity, переключения Fragment’ов или смены View внутри контейнера
- не обойтись без зависимости от Context’a, который не хочется передавать в слой логики, связывая тем самым его с платформой, усложняя тестирование и рискуя получить утечки памяти (если забыть очистить ссылку);
- надо учитывать жизненный цикл контейнера (например, java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState у Fragment’ов).
Поэтому и появилось решение, реализованное в Cicerone.
Начать, думаю, стоит со структуры.
Структура
На схеме есть четыре сущности:
- Command – это простейшая команда перехода, которую выполняет Navigator.
- Navigator – непосредственная реализация «переключения экранов» внутри контейнера.
- Router – это класс, который превращает высокоуровневые вызовы навигации презентера в набор Command.
- CommandBuffer – отвечает за сохранность вызванных команд навигации, если в момент их вызова нет возможности осуществить переход.
Теперь о каждой подробнее.
Команды переходов
Мы заметили, что любую карту переходов (даже достаточно сложную, как на первом изображении) можно реализовать, используя четыре базовых перехода, комбинируя которые, мы получим необходимое поведение.
Forward
Forward (String screenKey, Object transitionData) – команда, которая осуществляет переход на новый экран, добавляя его в текущую цепочку экранов.
screenKey – уникальный ключ, для каждого экрана.
transitionData – данные, необходимые новому экрану.
Буквой R обозначен корневой экран, его особенность только в том, что при выходе с этого экрана, мы выйдем из приложения.
Back
Back() – команда, удаляющая последний активный экран из цепочки, и возвращающая на предыдущий. При вызове на корневом экране ожидается выход из приложения.
BackTo
BackTo(String screenKey) – команда, позволяющая вернуться на любой из экранов в цепочке, достаточно указать его ключ. Если в цепочке два экрана с одинаковым ключом, то выбран будет последний (самый «правый»).
Стоит отметить, что если указанный экран не найден, либо в параметр ключа передать null, то будет осуществлен переход на корневой экран.
На практике эта команда очень удобна. Например, для авторизации: два экрана. Телефон -> СМС, а потом выход на тот, с которого была запущена авторизация.
Replace
Replace (String screenKey, Object transitionData) – команда, заменяющая активный экран на новый.
Кто-то может возразить, что этого результата удастся достичь, вызвав подряд команды Back и Forward, но тогда на корневом экране мы выйдем из приложения!
Вот и всё! Этих четырёх команд на практике достаточно для построения любых переходов. Но есть ещё одна команда, которая не относится к навигации, однако очень полезна на практике.
SystemMessage
SystemMessage (String message) – команда, отображающая системное сообщение (Alert, Toast, Snack и т. д.).
Иногда необходимо выйти с экрана и показать сообщение пользователю. Например, что мы сохранили сделанные изменения. Но экран, на который мы возвращаемся, не должен знать о чужой логике, и поэтому мы вынесли показ таких сообщений в отдельную команду. Это очень удобно!
Все команды отмечены интерфейсом-маркером Command. Если вам по какой-то причине понадобилась новая команда, просто создайте её, никаких ограничений!
Navigator
Команды сами по себе не реализуют переключение экранов, а только описывают эти переходы. За их выполнение отвечает Navigator.
public interface Navigator {
void applyCommand(Command command);
}
В зависимости от задачи, Navigator будет реализован по-разному, но он всегда будет там, где находится контейнер для переключаемых экранов.
- В Activity для переключения Fragment’ов.
- Во Fragment’е для переключения вложенных (child) Fragment’ов.
- … ваш вариант.
Так как в подавляющем большинстве Андроид приложений навигация опирается на переключение Fragment’ов внутри Activity, чтобы не писать однотипный код, в библиотеке уже есть готовый FragmentNavigator (и SupportFragmentNavigator для SupportFragment’ов), реализующий представленные команды.
Достаточно:
1) передать в конструктор ID контейнера и FragmentManager;
2) реализовать методы выхода из приложения и отображения системного сообщения;
3) реализовать создание Fragment’ов по screenKey.
За более подробным примером советую заглянуть в Sample-приложение.
В приложении необязательно должен быть один Navigator. Пример (тоже реальный, кстати): в Activity есть BottomBar, который доступен для пользователя ВСЕГДА. Но в каждом табе есть собственная навигация, которая сохраняется при переключении табов в BottomBar’е.
Решается это одним навигатором внутри Activity, который переключает табы, и локальными навигаторами внутри каждого Fragment’а-таба.
Таким образом, каждый отдельный презентер не завязан на то, где он находится: внутри цепочки одного из табов или в отдельном Activity. Достаточно предоставить ему правильный Router. Один Router связан только с одним Navigator’ом в любой момент времени. Об этом чуть дальше.
Router
Как было сказано выше, комбинируя команды, можно реализовать любой переход. Именно этой задачей и занимается Router.
Например, если стоит задача по некоторому событию в презентере:
1) скинуть всю цепочку до корневого экрана;
2) заменить корневой экран на новый;
3) и еще показать системное сообщение;
то в Router добавляется метод, который передает последовательность из трёх команд на выполнение в CommandBuffer:
public void navigateToNewRootWithMessage(String screenKey,
Object data,
String message) {
executeCommand(new BackTo(null));
executeCommand(new Replace(screenKey, data));
executeCommand(new SystemMessage(screenKey, data));
}
Если бы презентер сам вызывал эти методы, то после первой команды BackTo(), он был бы уничтожен (не совсем так, но суть передаёт) и не завершил работу корректно.
В библиотеке есть готовый Router, используемый по-умолчанию, с самыми необходимыми переходами, но как и с навигатором, никто не запрещает создать свою реализацию.
navigateTo() – переход на новый экран.
newScreenChain() – сброс цепочки до корневого экрана и открытие одного нового.
newRootScreen() – сброс цепочки и замена корневого экрана.
replaceScreen() – замена текущего экрана.
backTo() – возврат на любой экран в цепочке.
exit() – выход с экрана.
exitWithMessage() – выход с экрана + отображение сообщения.
showSystemMessage() – отображение системного сообщения.
CommandBuffer
CommandBuffer – класс, который отвечает за доставку команд навигации Navigator’у. Логично, что ссылка на экземпляр навигатора хранится в CommandBuffer’е. Она попадает туда через интерфейс NavigatorHolder:
public interface NavigatorHolder {
void setNavigator(Navigator navigator);
void removeNavigator();
}
Кроме того, если в CommandBuffer поступят команды, а в данный момент он не содержит Navigator’а, то они сохранятся в очереди, и будут выполнены сразу при установке нового Navigator’а. Именно благодаря CommandBuffer’у удалось решить все проблемы жизненного цикла.
Конкретный пример для Activity:
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
}
@Override
protected void onPause() {
SampleApplication.INSTANCE.getNavigatorHolder().removeNavigator();
super.onPause();
}
Почему именно onResume и onPause? Для безопасной транзакции Fragment’ов и отображения системного сообщения в виде алерта.
От теории к практике. Как использовать Cicerone?
Предположим, мы хотим реализовать навигацию на Fragment’ах в MainActivity:
Добавляем зависимость в build.gradle
repositories {
maven {
url 'https://dl.bintray.com/terrakok/terramaven/'
}
}
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:1.0'
}
В классе SampleApplication инициализируем готовый роутер
public class SampleApplication extends Application {
public static SampleApplication INSTANCE;
private Cicerone<Router> cicerone;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
cicerone = Cicerone.create();
}
public NavigatorHolder getNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
public Router getRouter() {
return cicerone.getRouter();
}
}
В MainActivity создаем навигатор:
private Navigator navigator = new SupportFragmentNavigator(getSupportFragmentManager(),
R.id.main_container) {
@Override
protected Fragment createFragment(String screenKey, Object data) {
switch(screenKey) {
case LIST_SCREEN:
return ListFragment.getNewInstance(data);
case DETAILS_SCREEN:
return DetailsFragment.getNewInstance(data);
default:
throw new RuntimeException(“Unknown screen key!”);
}
}
@Override
protected void showSystemMessage(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void exit() {
finish();
}
};
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
}
@Override
protected void onPause() {
super.onPause();
SampleApplication.INSTANCE.getNavigatorHolder().removeNavigator();
}
Теперь из любого места приложения (в идеале из презентера) можно вызывать методы роутера:
SampleApplication.INSTANCE.getRouter().backTo(...);
Частные случаи и их решение
Single Activity?
Нет! Но Activity я не рассматриваю как экраны, только как контейнеры. Смотрите: Router создан в классе Application, поэтому при переходе с одного Activity на другое, просто будет меняться активный навигатор, поэтому вполне можно делить приложение на независимые Activity, внутри которых будут уже переключения экранов. Конечно, стоит понимать, что цепочки экранов в таком случае будут привязаны к отдельным Activity, и команда BackTo() сработает только в контексте одного Activity.
Вложенная навигация
Я выше приводил пример, но повторюсь снова:
Есть Activity с табами. Стоит задача, чтобы внутри каждого таба была независимая цепочка экранов, сохраняющаяся при смене таба.
Решается это двумя типами навигации: глобальной и локальной.
GlobalRouter – роутер приложения, связанный с навигатором Activity.
Презентер, обрабатывающий клики по табам, вызывает команды у GlobalRouter.
LocalRouter – роутеры внутри каждого Fragment’а-контейнера. Навигатор для LocalRouter'а реализует сам Fragment-контейнер.
Презентеры, относящиеся к локальным цепочкам внутри табов, получают для навигации LocalRouter.
Где связь? Во Fragment’ах-контейнерах есть доступ и к глобальному навигатору! В момент, когда локальная цепочка внутри таба закончилась и вызвана команда Back(), то Fragment передает её в глобальный навигатор.
Совет: для настройки зависимостей между компонентами, используйте Dagger 2, а для управления их жизненным циклом – его CustomScopes.
А что с системной кнопкой Back?
Этот вопрос специально не решается в библиотеке. Нажатие на кнопку Back надо воспринимать как взаимодействие пользователя и передавать просто как событие в презентер.
Но есть ведь Flow или Conductor?
Мы смотрели другие решения, но отказались от них, так как одной из главных задач было использовать максимально стандартный подход и не создавать очередной фреймворк со своим FragmentManager’ом и BackStack’ом.
Во-первых, это позволит новым разработчикам быстро подключаться к проекту без необходимости изучения сторонних фреймворков.
Во-вторых, не придется всецело полагаться на сложные сторонние решения, что чревато затрудненной поддержкой.
Итог
Библиотека Cicerone:
- не завязана на Fragment’ы;
- не фреймворк;
- предоставляет короткие вызовы;
- легка в расширении;
- приспособлена для тестов;
- не зависит от жизненного цикла!
GitHub
Cicerone is a lightweight library that makes the navigation in an Android app easy.
Комментарии (27)
Strax
21.11.2016 11:46Добрый день, доклад действительно за интересовал, оданако появилось несколько вопросов:
1) Ок, команда SystemMessage однозначно решала какие-то ваши проблемы в проекте, однако встает 2 вопроса 1: насколько это вообще зона ответственности навигатора 2: если все же его, то почему насколько скудно, тогда уж надо разрешать ему в принципе разруливать логику startActivityForResult, то есть позволять перебрасывать некоторые объекты между экранами.
2) Мне очень понравилась Ваша идея разруливать навигацию в стороне от вьюх, однако (огромное) ограничение, связаное с тем что Activity это не экран, ломает всю красоту (на мой взгляд), хотелось бы единообразно работать с любым стеком навигаций в не зависимости от того с чем ты работаешь (Activity, Fragment and so on), а так получается что фрагменты и Android View используют Ваш подход, а Activity нет. Не могли бы Вы рассказать в чем причина? Почему Активити это не экран?terrakok
21.11.2016 12:29Про команду SystemMessage.
Я специально выделил ей особое внимание, так как она только косвенно связана с навигацией. Можно обойтись и без нее, но в ряде случаев это будет не так изящно как с ней!
Пример: форма оплаты. Пользователь заполнил поля и нажал далее, но сервер вернул ошибку авторизации. Нам надо сказать об этом пользователю и перекинуть на экран входа. Без команды SystemMessage это можно сделать двумя способами:
- на экране оплаты показать диалог и ждать нажатия кнопки ОК. Тут придется блокировать отмену этого диалога + очищать поля с секретной информацией, так как ошибка авторизрции может говорить о том, что приложение в чужих руках.
- переходить на экран входа с дополнительной информацией о том, что надо при запуске показать сообщение. Это само по себе некрасиво, так как, по логике, экран входа не должен ничего иного делать, кроме как регистрировать пользователя.
Разруливание логики startActivityForResult оставлено на решение в частном порядке, так как эта логика доступна только в Activity и частично во Fragment'ах, а библиотека не делает разницы между разными видами View. Как я и говорил, Cicerone легко расширять, все в ваших руках.
Важный момент: в подходе MVP не предполагается обмена данными между View и даже между Presenter'ами. Это надо реализовывать через модель. Да, в андроиде Activity обладают механизмом startActivityForResult, но он не единственно возможный. Если речь о сторонних библиотеках или системных Activity, то я советую рассматривать результат запуска таких Activity как обычное пользовательское действие и передавать данные в свой презентер, где уже делать остальную обработку.
"Activity это не экран" — кто сказал? Activity может быть View без каких либо ограничений! После перехода на новое Activity, первое Activity очистит навигатор в методе onPause, а новое Activity установит свой навигатор в onResume. Здесь будет только один момент, обусловленный архитектурой андроида: Activity будет, с одной стороны, реализовывать View, а с другой, заниматься навигацией. (помните слайд с Франкенштейном?)
terrakok
21.11.2016 23:37Я добавил в Sample приложение пример навигации на Activity. Welcome!
Xanderblinov
23.11.2016 21:52Без команды SystemMessage это можно сделать двумя способами
А еще есть третий способ, презентер, на который возвращаемся, подписывается на ивенты/обсервер модели о протухании авторизации.
Таким образом, при возврате на экран он примет состояние: показать ошибку авторизацииterrakok
23.11.2016 22:22А если это другая ошибка? Тогда нам надо все презентеры подписать на все ошибки? Я же говорю, решить можно по-разному, но SystemMessage поможет в некоторых случаях сделать это проще и изящнее.
Еще хочу подчеркнуть важную деталь: SystemMessage создана для показа сообщений именно при навигации, то есть это можно рассматривать как некоторое состояние в навигации. Неправильно использовать эту команду просто для показа сообщений на экранах. Это ответственность Вью. SystemMessage не умеет обрабатывать клик или еще что-то, это только сообщение пользователю о случившемся переходе.
evnik
23.11.2016 02:18Этих четырёх команд на практике достаточно для построения любых переходов
Не очень понятно что делать, если после успешной аунтефикации мне нужно переходить на новый экран, а не возвращаться на предыдущий? Или как запретить возвращаться на один из предыдущих экранов (не запрещая переход к корневому)? Команда типа
RemoveBackScreen(String screenKey)
могла бы решить обе эти задачи, а также с ней можно реализовать командуReplace
.terrakok
23.11.2016 02:261) Если я вас правильно понял, то вы спрашиваете про такой случай:
до перехода (F — активный экран)
a -> b -> c -> d -> e -> F
после перехода (G — активный экран)
a -> b -> c -> d -> G
Это решается методом в роутере:
public void customTransition() { backTo("d"); forward("g"); }
2) Запрещать возвращаться на экран? Это как? Зачем тогда его оставлять в цепочке?
evnik
23.11.2016 04:131) Да. Так можно решить, но хотелось бы избавиться от перехода назад к
d
. Мы ведь на самом деле не хотим его отображать. Кроме того, откуда нам знать, что тамd
? У нас нет ограничений на место откуда запущена аутентификация.
2) Не надо оставлять его в цепочке. Но убрать нужно уже после того, как он оказался в ее середине. Абстрактный пример (не очень хороший дизайн, просто для примера):
- Зайти в профиль контакта
- Зайти в группу контактов
- Зайти в другой профиль
- Зайти в группу контактов
... - Зайти опять в профиль контакта из пункта 1
- Удалить контакт
- Возвращаться назад до корневого экрана
При возврате в экран из пункта 1 показывать будет нечего (контакт уже удален). Хорошо бы удалить этот экран из цепочки сразу при удалении контакта.
terrakok
23.11.2016 11:031) Экран D показан не будет, так как смена экранов происходит быстрее. И все зависит от задачи. Можно сделать возврат не к конкретному экрану, а "на два назад", если экраны аутентификации попадают в общую цепочку. Такие случаи решаются расширением роутера. Для этого библиотека и создавалась гибкой.
Другой вариант: делать аутентификацию в отдельном активити с локальной цепочкой экранов.
2) Вы описываете типичный кейс бизнес логики. Если вы хотите, можно создать для таких случаев отдельную команду навигации. Повторюсь, что все для этого готово. Библиотека включает в себя необходимый минимум, если стараться учесть все кейсы, то маленькое решение превратится в гигантского монстра!
Возможно в будущем мы и добавим что-то еще, но на данный момент это не кажется критичным
evnik
23.11.2016 22:12Библиотека включает в себя необходимый минимум, если стараться учесть все кейсы, то маленькое решение превратится в гигантского монстра!
Все верно. Просто по моему мнению, вместо команды
Replace
хорошо бы иметь что-то вродеRemoveBackScreen
. Библиотека останется маленькой, но станет более гибкой.terrakok
23.11.2016 22:26RemoveBackScreen не решить явным образом при навигации по Активити.
А если чрезмерная гибкость может создать неоправданные сложности, то лучше ограничится на Replace.
Попробуйте сделать RemoveBackScreen, а там посмотрим :)
Jeevuz
23.11.2016 13:46Этих четырёх команд на практике достаточно для построения любых переходов
Тут скорее имелось в виду, что их достаточно для построения практически любых переходов.
Конечно, может появиться какой-то специфический кейс, который будет удобнее решить по-другому.
И для этого можно всегда легко добавить свою команду. И сделать свой вариант роутера, в котором добавить переход, использующий эту команду или комбинирующий её с остальными. Но сперва надо посмотреть нельзя ли решить этот кейс встроенными командами (мы же все ленивые).
Для меня самое прекрасное в либе как раз то, что она маленькая и гибкая.
evnik
23.11.2016 22:37не обойтись без зависимости от Context’a, который не хочется передавать в слой логики… рискуя получить утечки памяти (если забыть очистить ссылку)
А как можно получить утечку памяти, если
Context
все равно живет всегда? Или речь идет обActivity
, а не оContext.getApplicationContext
?terrakok
23.11.2016 23:39Речь о контексте необходимом для навигации, например Activity, у которого брать FragmentManager и так далее
Axel_Vasilyev
24.11.2016 15:06Спасибо за отличное решение. Очень понравился подход к навигации. В процессе тестирования возникла одна проблема. Ваш пример использует библиотеку Moxy для реализации mvp. В связи с этим получается нестыковка такого плана, что Moxy повторяет команды для новой view, присоединяющейся к presenter. Команды по переключению фрагментов реализовал с помощью Вашей библиотеки. Но при пересоздании активити они не повторяются, т.е. мы теряем преимущество Moxy с их очередью команд.
Попытка переопределить Router и добавить очередь не увенчалась успехом, потому что, как я думаю, переопределять нужно ещё и CommandBuffer, именно в нем логично добавить очередь команд и повторять её.
А также необходимо очищать очередь в случае смены rootScreen.
Можете что-нибудь посоветовать?terrakok
24.11.2016 15:12Cicerone решает проблему передачи навигационных вызовов, но не сохранения стека навигации. Cicerone отвечает за то, что команды навигации гарантировано дойдут до навигатора, но не сохраняет их для полного восстановления состояния приложения. (представьте как восстановить цепочку из смеси Activity и Fragment'ов)
Мы не хотели писать свой FragmentManager + ActivityStack + и тд.
У себя мы тоже используем Moxy. А за восстановление навигации отвечает сам андроид.Axel_Vasilyev
24.11.2016 15:27В том и дело, что андроид не верно восстанавливает навигацию в некоторых случаях.
Попробую объяснить, что я пытаюсь сказать.
Мы исходим из того, что у нас есть уже реализованные простые команды. (С помощью них действительно можно реализовать большую кучу сценариев)
Я подумывал о том, каждая из команд может быть двух типов: 1) корневая команда (например, newRootScreen и newScreenChain) — она должна очищать очередь команд, т.к. предыдущие команды переходов бессмысленно повторять; 2) простая команда (например, navigateTo) — она просто заноситься в очередь.
Таким образом, мы получаем четкую очередь команд до корня и может даже нет смысла повторять их все при переподключении view к presenter, но мне кажется их стоит сохранять.
Допустим при нажатии назад, мы сможем подставить нужную команду из очереди.terrakok
24.11.2016 15:41Ничего вам не мешает хранить команды. Они все приходят в навигатор, здесь можно придумать какой-то массив команд и складывать его в бандл при умирании.
Такие вещи могут превратить библиотеку во фреймворк, то есть внутри будет много собственной логики о которой надо будет постоянно помнить, а кому-то она может не подойти и тогда придется опять переделывать.
Jeevuz
24.11.2016 15:34Но при пересоздании активити они не повторяются, т.е. мы теряем преимущество Moxy с их очередью команд.
Что-то не совсем понятно, что вы имеете в виду, можете описать иначе?
Попытка переопределить Router и добавить очередь.
Очередь невыполненных команд живет в CommandBuffer. Для чего вам очередь в рутере?
А также необходимо очищать очередь в случае смены rootScreen.
А для чего?
Из того что я понял, вы пытаетесь восстановить состояние стека экранов при помощи Cicerone, а состояние экрана средствами Moxy. Но зачем вам это если FragmentManager и так восстановит состояние бекстека я не понимаю. Или вы делаете без фрагментов?
Axel_Vasilyev
24.11.2016 15:451) у меня есть активити и несколько фрагментов (переключение из BottomBar), при повороте экрана Moxy следит, чтобы была выделена нужная иконка фрагмента, а фрагмент может загрузиться не тот (команда открытия нужного фрагмента не повторяется)
2) я пытался унаследоваться от Router, чтобы добавить очередь, адекватного ничего не вышло. Очередь нужно добавлять в класс CommandBuffer. Там только очередь НЕВЫПОЛНЕННЫХ команд, а мне нужно повторить ВЫПОЛНЕННЫЕ при повороте экрана.
3) пока делаю с фрагментами, но в будущем может будет и без них. я просто предлагаю логику для реализации очередиJeevuz
24.11.2016 16:05+11) Думаю тут проблема не в либе и не в том, что надо восстанавливать команды. Не надо. Нужно разобраться почему не восстанавливается нужный фрагмент. Он должен восстанавливаться силами FragmentManager и android. Ищите проблему в своей реализации. Потому что то, о чем говорите вы все верно отрабатывает в sample-приложении. Можно уйти далеко по стеку фрагментов и повернуть и все восстановится. Силами cистемы. Даже при включенном "не сохранять действия".
2) Я понимаю, что вы хотели сделать. Реализовать сохранение состояния стека экранов сохраняя последовательность навигационных команд. Как Moxy cохраняет состояние вью. Но это задача для отдельной либы или форка. Мы же наоборот хотели, чтобы либа была простой. Если нужно что-то более сложное — посмотрите в сторону Flow и Conductor.
3) Библиотека не держит очередь в себе. Это было изначальной мыслью при ее создании. Она дает интерфейс команд, механизм их комбинирования и пересылки. Все. Реализовать команды, хнанение стека экранов, восстановление стека — это все задачи за переделом ответственности библиотеки. В случае с фрагментами все уже сделано в андроиде, а мы даем дефолтную имлементацию навигатора. В случае с вьюхами это надо будет все делать самостоятельно. Либо силами другой либы.
Axel_Vasilyev
24.11.2016 21:26Я Вас понял. Спасибо за ответ. Поищу ошибки в реализации. Возможно посмотрю в сторону других решений.
Но Ваш проект определенно заслуживает внимания.Jeevuz
25.11.2016 10:30Спасибо за такую высокую оценку! Очень приятно.
А вам быстро разобраться, в чем проблема и приятного кодинга!
terrakok
28.11.2016 17:35Для всех интересующихся: добавил в Sample приложение экран со сложной параллельной навигацией (в каждом табе свой стек экранов).
Firsto
Только что были на вашем докладе «Навигация без боли и слёз» на DevFest в Новосибирске. Библиотека оказалась весьма интересной, очень понравился такой достаточно прозрачный поход к навигации. Возвращаемся домой, внедрять Cicerone в свои проекты. Спасибо, что делаете нашу жизнь лучше!
terrakok
Рад, что заинтересовал вас!