Навигация в android без боли и слез
TRANSCRIPT
Навигация без боли и слез
Константин Цховребов 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()
View Presenter load()
request()
Presenter
request()
View
Presenter
request() response()
View
Presenter
Router
request() response()
View
Presenter
RouterImpl Command
Queue
request() response()
View
View Presenter
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: [email protected] telegram: @terrakok https://github.com/terrakok/Cicerone