Навигация
без боли и слез
Константин Цховребов
Android Team Leader
Первый большой проект на MVP
В теории все прекрасно!
Первый большой проект на MVP
В теории все прекрасно!
Первый большой проект на MVP
На практике встречаешь ANDROID
Навигация в андроид приложении
Навигация в андроид приложении
●  Переход между экранами
Навигация в андроид приложении
●  Переход между экранами
●  Экраны сменяются в некотором контейнере
Навигация в андроид приложении
●  Переход между экранами
●  Экраны сменяются в некотором контейнере
●  Для совершения перехода нужен Context
А что если Fragment
сделать презентером?
А что если Fragment
сделать презентером?
1.  Lifecycle
А что если Fragment
сделать презентером?
1.  Lifecycle
2.  Нужен универсальный подход
(Activity/Fragment/View)
А что если Fragment
сделать презентером?
1.  Lifecycle
2.  Нужен универсальный подход
(Activity/Fragment/View)
3.  Context (и другие классы)
Mosby (http://hannesdorfmann.com/mosby/)
public interface LoginView extends MvpView {
public void showLoginForm();
public void showError();
public void showLoading();
public void loginSuccessful();
}
	
public class LoginFragment … implements
LoginView {
…
// Called when login was successful
@Override public void loginSuccessful() {
getActivity().finish();
}
…
}
Готовые решения
Flow
(https://github.com/square/flow)
Conductor
(https://github.com/bluelinelabs/Conductor)
Требования к идеальной навигации
Требования к идеальной навигации
●  прямой доступ из презентера
Требования к идеальной навигации
●  прямой доступ из презентера
●  не завязана на фрагменты
Требования к идеальной навигации
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
Требования к идеальной навигации
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
Требования к идеальной навигации
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
Требования к идеальной навигации
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
Требования к идеальной навигации
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
Это реальный проект!
Команды переходов
Команды переходов
Forward(String screenKey, Object transitionData);
Команды переходов
Forward(String screenKey, Object transitionData);
Back();
Команды переходов
Forward(String screenKey, Object transitionData);
Back();
BackTo(String screenKey);
Команды переходов
Forward(String screenKey, Object transitionData);
Back();
BackTo(String screenKey);
Replace(String screenKey, Object transitionData);
SystemMessage
SystemMessage(String message);
Navigator
public interface Navigator {
void applyCommand(Command command);
}
Navigator
public interface Navigator {
void applyCommand(Command command);
}
public class MainActivity extends Activity {
private Navigator navigator = new Navigator() {
@Override
public void applyCommand(Command command) {
...
}
}
}
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
public class Presenter {
@Inject
Navigator navigator;
private void next() {
navigator.applyCommand(new Forward(“Some screen”));
}
}
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
public class Presenter {
@Inject
Navigator navigator;
private void authError() {
navigator.applyCommand(new BackTo(null));
navigator.applyCommand(new Replace("Login screen", null));
navigator.applyCommand(new SystemMessage("Token expired!"));
}
}
Router
public class Router extends BaseRouter {
void newRootScreenWithMessage(String screenKey, Object data, String message) {...}
}
Router
public class Router extends BaseRouter {
void newRootScreenWithMessage(String screenKey, Object data, String message) {...}
}
public class Presenter {
@Inject
Router router;
private void authError() {
router.newRootScreenWithMessage("Login screen", null, "Token expired!");
}
}
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
public class Router extends BaseRouter {
private Navigator navigator;
public void setNavigator(Navigator navigator) {
this.navigator = navigator;
}
public void removeNavigator() {
this.navigator = null;
}
public void newRootScreenWithMessage(String screenKey, Object data, String message) {
if (navigator != null) {
navigator.applyCommand(new BackTo(null));
navigator.applyCommand(new Replace("Login screen", null));
navigator.applyCommand(new SystemMessage("Token expired!"));
}
}
}
public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
router.setNavigator(navigator);
}
@Override
protected void onPause() {
super.onPause();
router.removeNavigator();
}
}
Presenter View
load()
ViewPresenter
load()
request()
Presenter
request()
View
Presenter
request() response()
View
Presenter
Router
request() response()
View
Presenter
RouterImpl
Command
Queue
request() response()
View
ViewPresenter
RouterImpl
Command
Queue
request() response()
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
✓
Unit тестирование
@Test
public void someTest() throws Exception {
Router mockRouter = mock(Router.class);
Presenter presenter = new Presenter();
presenter.onNextButtonClicked();
verify(mockRouter, times(1)).navigateTo(
eq("Some screen"),
argument.capture()
);
}
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
✓
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
✓
✓
Расширение возможностей
●  в большинстве случаев достаточно добавить
метод в Router и реализовать его с помощью
Command
●  реже необходимо создать новую Command
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
✓
✓
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
✓
✓
✓
//TODO:
●  прямой доступ из презентера
●  не завязана на фрагменты
●  не фреймворк
●  короткие вызовы
●  легка в расширении
●  приспособлена для тестов
●  не зависит от жизненного цикла!
✓
✓
✓
✓
✓
✓
✓
Cicerone
Чичероне - (устар.) гид для иностранцев
https://github.com/terrakok/Cicerone
repositories {
maven {
url 'https://dl.bintray.com/terrakok/terramaven/'
}
}
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:1.0'
}
Для кого мы ее создали?
Для кого мы ее создали?
Для себя :)
Для кого мы ее создали?
Почему она может пригодиться вам?
Для себя :)
Для кого мы ее создали?
●  В приложении нелинейная навигация
Почему она может пригодиться вам?
Для себя :)
Для кого мы ее создали?
●  В приложении нелинейная навигация
●  Отделение логики от отображения
Почему она может пригодиться вам?
Для себя :)
Для кого мы ее создали?
●  В приложении нелинейная навигация
●  Отделение логики от отображения
●  Сохранение вызовов навигации после
возвращения к приложению
Почему она может пригодиться вам?
Для себя :)
Возможности Cicerone
public class Router extends BaseRouter {
void navigateTo(String screenKey, Object data);
void newScreenChain(String screenKey, Object data);
void newRootScreen(String screenKey, Object data);
void replaceScreen(String screenKey, Object data);
void backTo(String screenKey);
void exit();
void exitWithMessage(String message);
void showSystemMessage(String message);
}
public abstract class FragmentNavigator implements Navigator {}
Спасибо за внимание! Вопросы?
email: terrakok@mobileup.ru
telegram: @terrakok
https://github.com/terrakok/Cicerone

Навигация в Android без боли и слез

  • 1.
    Навигация без боли ислез Константин Цховребов Android Team Leader
  • 3.
  • 4.
    В теории всепрекрасно! Первый большой проект на MVP
  • 5.
    В теории всепрекрасно! Первый большой проект на MVP На практике встречаешь ANDROID
  • 6.
  • 7.
    Навигация в андроидприложении ●  Переход между экранами
  • 8.
    Навигация в андроидприложении ●  Переход между экранами ●  Экраны сменяются в некотором контейнере
  • 9.
    Навигация в андроидприложении ●  Переход между экранами ●  Экраны сменяются в некотором контейнере ●  Для совершения перехода нужен Context
  • 10.
    А что еслиFragment сделать презентером?
  • 11.
    А что еслиFragment сделать презентером? 1.  Lifecycle
  • 12.
    А что еслиFragment сделать презентером? 1.  Lifecycle 2.  Нужен универсальный подход (Activity/Fragment/View)
  • 13.
    А что еслиFragment сделать презентером? 1.  Lifecycle 2.  Нужен универсальный подход (Activity/Fragment/View) 3.  Context (и другие классы)
  • 14.
    Mosby (http://hannesdorfmann.com/mosby/) public interfaceLoginView extends MvpView { public void showLoginForm(); public void showError(); public void showLoading(); public void loginSuccessful(); } public class LoginFragment … implements LoginView { … // Called when login was successful @Override public void loginSuccessful() { getActivity().finish(); } … }
  • 15.
  • 16.
  • 17.
    Требования к идеальнойнавигации ●  прямой доступ из презентера
  • 18.
    Требования к идеальнойнавигации ●  прямой доступ из презентера ●  не завязана на фрагменты
  • 19.
    Требования к идеальнойнавигации ●  прямой доступ из презентера ●  не завязана на фрагменты ●  не фреймворк
  • 20.
    Требования к идеальнойнавигации ●  прямой доступ из презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы
  • 21.
    Требования к идеальнойнавигации ●  прямой доступ из презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении
  • 22.
    Требования к идеальнойнавигации ●  прямой доступ из презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов
  • 23.
    Требования к идеальнойнавигации ●  прямой доступ из презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла!
  • 24.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла!
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
    Команды переходов Forward(String screenKey,Object transitionData); Back(); BackTo(String screenKey);
  • 30.
    Команды переходов Forward(String screenKey,Object transitionData); Back(); BackTo(String screenKey); Replace(String screenKey, Object transitionData);
  • 31.
  • 32.
    Navigator public interface Navigator{ void applyCommand(Command command); }
  • 33.
    Navigator public interface Navigator{ void applyCommand(Command command); } public class MainActivity extends Activity { private Navigator navigator = new Navigator() { @Override public void applyCommand(Command command) { ... } } }
  • 34.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла!
  • 35.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓
  • 36.
    public class Presenter{ @Inject Navigator navigator; private void next() { navigator.applyCommand(new Forward(“Some screen”)); } }
  • 37.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓
  • 38.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓
  • 41.
    public class Presenter{ @Inject Navigator navigator; private void authError() { navigator.applyCommand(new BackTo(null)); navigator.applyCommand(new Replace("Login screen", null)); navigator.applyCommand(new SystemMessage("Token expired!")); } }
  • 42.
    Router public class Routerextends BaseRouter { void newRootScreenWithMessage(String screenKey, Object data, String message) {...} }
  • 43.
    Router public class Routerextends BaseRouter { void newRootScreenWithMessage(String screenKey, Object data, String message) {...} } public class Presenter { @Inject Router router; private void authError() { router.newRootScreenWithMessage("Login screen", null, "Token expired!"); } }
  • 44.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓
  • 45.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓
  • 46.
    public class Routerextends BaseRouter { private Navigator navigator; public void setNavigator(Navigator navigator) { this.navigator = navigator; } public void removeNavigator() { this.navigator = null; } public void newRootScreenWithMessage(String screenKey, Object data, String message) { if (navigator != null) { navigator.applyCommand(new BackTo(null)); navigator.applyCommand(new Replace("Login screen", null)); navigator.applyCommand(new SystemMessage("Token expired!")); } } }
  • 47.
    public class MainActivityextends Activity { @Override protected void onResume() { super.onResume(); router.setNavigator(navigator); } @Override protected void onPause() { super.onPause(); router.removeNavigator(); } }
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓
  • 56.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓ ✓
  • 57.
    Unit тестирование @Test public voidsomeTest() throws Exception { Router mockRouter = mock(Router.class); Presenter presenter = new Presenter(); presenter.onNextButtonClicked(); verify(mockRouter, times(1)).navigateTo( eq("Some screen"), argument.capture() ); }
  • 58.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓ ✓
  • 59.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓
  • 60.
    Расширение возможностей ●  вбольшинстве случаев достаточно добавить метод в Router и реализовать его с помощью Command ●  реже необходимо создать новую Command
  • 61.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓
  • 62.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓ ✓
  • 63.
    //TODO: ●  прямой доступиз презентера ●  не завязана на фрагменты ●  не фреймворк ●  короткие вызовы ●  легка в расширении ●  приспособлена для тестов ●  не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓ ✓ ✓
  • 64.
    Cicerone Чичероне - (устар.)гид для иностранцев https://github.com/terrakok/Cicerone repositories { maven { url 'https://dl.bintray.com/terrakok/terramaven/' } } dependencies { //Cicerone compile 'ru.terrakok.cicerone:cicerone:1.0' }
  • 66.
    Для кого мыее создали?
  • 67.
    Для кого мыее создали? Для себя :)
  • 68.
    Для кого мыее создали? Почему она может пригодиться вам? Для себя :)
  • 69.
    Для кого мыее создали? ●  В приложении нелинейная навигация Почему она может пригодиться вам? Для себя :)
  • 70.
    Для кого мыее создали? ●  В приложении нелинейная навигация ●  Отделение логики от отображения Почему она может пригодиться вам? Для себя :)
  • 71.
    Для кого мыее создали? ●  В приложении нелинейная навигация ●  Отделение логики от отображения ●  Сохранение вызовов навигации после возвращения к приложению Почему она может пригодиться вам? Для себя :)
  • 72.
    Возможности Cicerone public classRouter extends BaseRouter { void navigateTo(String screenKey, Object data); void newScreenChain(String screenKey, Object data); void newRootScreen(String screenKey, Object data); void replaceScreen(String screenKey, Object data); void backTo(String screenKey); void exit(); void exitWithMessage(String message); void showSystemMessage(String message); } public abstract class FragmentNavigator implements Navigator {}
  • 73.
    Спасибо за внимание!Вопросы? email: terrakok@mobileup.ru telegram: @terrakok https://github.com/terrakok/Cicerone