Подробная статья по докладу: https://habrahabr.ru/company/mobileup/blog/314838/
Team Lead MobileUp Константин Цховребов выступил в Новосибирске на IT-конференций DevFest.
Поделиться азами гибкой простой и функциональной навигации по экранам при использовании MVP в Android. Рассказал, как сделать код навигации чистым и lifecycle-безопасным, а любую, даже самую навороченную цепочку переходов по экранам – делом пары строк.
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 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();
}
…
}
18. Требования к идеальной навигации
● прямой доступ из презентера
● не завязана на фрагменты
19. Требования к идеальной навигации
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
20. Требования к идеальной навигации
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
21. Требования к идеальной навигации
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
22. Требования к идеальной навигации
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
23. Требования к идеальной навигации
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
24. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
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:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
42. Router
public class Router extends BaseRouter {
void newRootScreenWithMessage(String screenKey, Object data, String message) {...}
}
43. 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!");
}
}
44. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
45. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
46. 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!"));
}
}
}
55. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
56. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
✓
57. 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()
);
}
58. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
✓
59. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
✓
✓
60. Расширение возможностей
● в большинстве случаев достаточно добавить
метод в Router и реализовать его с помощью
Command
● реже необходимо создать новую Command
61. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
✓
✓
62. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
✓
✓
✓
63. //TODO:
● прямой доступ из презентера
● не завязана на фрагменты
● не фреймворк
● короткие вызовы
● легка в расширении
● приспособлена для тестов
● не зависит от жизненного цикла!
✓
✓
✓
✓
✓
✓
✓
68. Для кого мы ее создали?
Почему она может пригодиться вам?
Для себя :)
69. Для кого мы ее создали?
● В приложении нелинейная навигация
Почему она может пригодиться вам?
Для себя :)
70. Для кого мы ее создали?
● В приложении нелинейная навигация
● Отделение логики от отображения
Почему она может пригодиться вам?
Для себя :)
71. Для кого мы ее создали?
● В приложении нелинейная навигация
● Отделение логики от отображения
● Сохранение вызовов навигации после
возвращения к приложению
Почему она может пригодиться вам?
Для себя :)
72. Возможности 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 {}
73. Спасибо за внимание! Вопросы?
email: terrakok@mobileup.ru
telegram: @terrakok
https://github.com/terrakok/Cicerone