Slide 1

Slide 1 text

Навигация без боли и слез Константин Цховребов Android Team Leader

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Первый большой проект на MVP

Slide 4

Slide 4 text

В теории все прекрасно! Первый большой проект на MVP

Slide 5

Slide 5 text

В теории все прекрасно! Первый большой проект на MVP На практике встречаешь ANDROID

Slide 6

Slide 6 text

Навигация в андроид приложении

Slide 7

Slide 7 text

Навигация в андроид приложении ● Переход между экранами

Slide 8

Slide 8 text

Навигация в андроид приложении ● Переход между экранами ● Экраны сменяются в некотором контейнере

Slide 9

Slide 9 text

Навигация в андроид приложении ● Переход между экранами ● Экраны сменяются в некотором контейнере ● Для совершения перехода нужен Context

Slide 10

Slide 10 text

А что если Fragment сделать презентером?

Slide 11

Slide 11 text

А что если Fragment сделать презентером? 1. Lifecycle

Slide 12

Slide 12 text

А что если Fragment сделать презентером? 1. Lifecycle 2. Нужен универсальный подход (Activity/Fragment/View)

Slide 13

Slide 13 text

А что если Fragment сделать презентером? 1. Lifecycle 2. Нужен универсальный подход (Activity/Fragment/View) 3. Context (и другие классы)

Slide 14

Slide 14 text

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(); } … }

Slide 15

Slide 15 text

Готовые решения Flow (https://github.com/square/flow) Conductor (https://github.com/bluelinelabs/Conductor)

Slide 16

Slide 16 text

Требования к идеальной навигации

Slide 17

Slide 17 text

Требования к идеальной навигации ● прямой доступ из презентера

Slide 18

Slide 18 text

Требования к идеальной навигации ● прямой доступ из презентера ● не завязана на фрагменты

Slide 19

Slide 19 text

Требования к идеальной навигации ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк

Slide 20

Slide 20 text

Требования к идеальной навигации ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы

Slide 21

Slide 21 text

Требования к идеальной навигации ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении

Slide 22

Slide 22 text

Требования к идеальной навигации ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов

Slide 23

Slide 23 text

Требования к идеальной навигации ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла!

Slide 24

Slide 24 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла!

Slide 25

Slide 25 text

Это реальный проект!

Slide 26

Slide 26 text

Команды переходов

Slide 27

Slide 27 text

Команды переходов Forward(String screenKey, Object transitionData);

Slide 28

Slide 28 text

Команды переходов Forward(String screenKey, Object transitionData); Back();

Slide 29

Slide 29 text

Команды переходов Forward(String screenKey, Object transitionData); Back(); BackTo(String screenKey);

Slide 30

Slide 30 text

Команды переходов Forward(String screenKey, Object transitionData); Back(); BackTo(String screenKey); Replace(String screenKey, Object transitionData);

Slide 31

Slide 31 text

SystemMessage SystemMessage(String message);

Slide 32

Slide 32 text

Navigator public interface Navigator { void applyCommand(Command command); }

Slide 33

Slide 33 text

Navigator public interface Navigator { void applyCommand(Command command); } public class MainActivity extends Activity { private Navigator navigator = new Navigator() { @Override public void applyCommand(Command command) { ... } } }

Slide 34

Slide 34 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла!

Slide 35

Slide 35 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓

Slide 36

Slide 36 text

public class Presenter { @Inject Navigator navigator; private void next() { navigator.applyCommand(new Forward(“Some screen”)); } }

Slide 37

Slide 37 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓

Slide 38

Slide 38 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

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!")); } }

Slide 42

Slide 42 text

Router public class Router extends BaseRouter { void newRootScreenWithMessage(String screenKey, Object data, String message) {...} }

Slide 43

Slide 43 text

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!"); } }

Slide 44

Slide 44 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓

Slide 45

Slide 45 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓

Slide 46

Slide 46 text

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!")); } } }

Slide 47

Slide 47 text

public class MainActivity extends Activity { @Override protected void onResume() { super.onResume(); router.setNavigator(navigator); } @Override protected void onPause() { super.onPause(); router.removeNavigator(); } }

Slide 48

Slide 48 text

Presenter View load()

Slide 49

Slide 49 text

View Presenter load() request()

Slide 50

Slide 50 text

Presenter request() View

Slide 51

Slide 51 text

Presenter request() response() View

Slide 52

Slide 52 text

Presenter Router request() response() navigate() View

Slide 53

Slide 53 text

Presenter RouterImpl Command Queue request() response() navigate() View

Slide 54

Slide 54 text

View Presenter RouterImpl Command Queue request() response() navigate()

Slide 55

Slide 55 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓

Slide 56

Slide 56 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓ ✓

Slide 57

Slide 57 text

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() ); }

Slide 58

Slide 58 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓ ✓

Slide 59

Slide 59 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓

Slide 60

Slide 60 text

Расширение возможностей ● в большинстве случаев достаточно добавить метод в Router и реализовать его с помощью Command ● реже необходимо создать новую Command

Slide 61

Slide 61 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓

Slide 62

Slide 62 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓ ✓

Slide 63

Slide 63 text

//TODO: ● прямой доступ из презентера ● не завязана на фрагменты ● не фреймворк ● короткие вызовы ● легка в расширении ● приспособлена для тестов ● не зависит от жизненного цикла! ✓ ✓ ✓ ✓ ✓ ✓ ✓

Slide 64

Slide 64 text

Cicerone Чичероне - (устар.) гид для иностранцев https://github.com/terrakok/Cicerone repositories { maven { url 'https://dl.bintray.com/terrakok/terramaven/' } } dependencies { //Cicerone compile 'ru.terrakok.cicerone:cicerone:1.0' }

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

Для кого мы ее создали?

Slide 67

Slide 67 text

Для кого мы ее создали? Для себя :)

Slide 68

Slide 68 text

Для кого мы ее создали? Почему она может пригодиться вам? Для себя :)

Slide 69

Slide 69 text

Для кого мы ее создали? ● В приложении нелинейная навигация Почему она может пригодиться вам? Для себя :)

Slide 70

Slide 70 text

Для кого мы ее создали? ● В приложении нелинейная навигация ● Отделение логики от отображения Почему она может пригодиться вам? Для себя :)

Slide 71

Slide 71 text

Для кого мы ее создали? ● В приложении нелинейная навигация ● Отделение логики от отображения ● Сохранение вызовов навигации после возвращения к приложению Почему она может пригодиться вам? Для себя :)

Slide 72

Slide 72 text

Возможности 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 {}

Slide 73

Slide 73 text

Спасибо за внимание! Вопросы? email: [email protected] telegram: @terrakok https://github.com/terrakok/Cicerone