SlideShare a Scribd company logo
1 of 44
А
MVP, Moxy
Как правильно
пользоваться Yuri Shmakov
Android Team leader
Arello Mobile
Russia
Moxy
Общий подход
• Определить Presenter
• Определить интерфейс View
• Реализовать View
• Выделить бизнес-логику в Model
Moxy
Требования к решениям задач
• Автоматическое сохранение отображения при пересоздании View
• Никакого boilerplate-кода
• Отсутствие ненужных флажков, id, switch-case, e.t.c.
Moxy
Задача #1
• Приложение запускается
• Проходит 1 секунда
• Отображается сообщение
Moxy
public interface HelloWorldView extends MvpView {
void showMessage(int message);
}
Moxy
@InjectViewState
public class HelloWorldPresenter extends MvpPresenter<HelloWorldView> {
public HelloWorldPresenter() {
AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
sleepSecond();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
getViewState().showMessage(R.string.hello_world);
}
private void sleepSecond() {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ignore) {}
}
};
asyncTask.execute();
}
}
Moxy
public class MainActivity extends MvpAppCompatActivity implements HelloWorldView {
@InjectPresenter
HelloWorldPresenter mHelloWorldPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void showMessage(int message) {
TextView messageTextView = new TextView(this);
messageTextView.setText(message);
messageTextView.setTextSize(40);
messageTextView.setGravity(Gravity.CENTER_HORIZONTAL);
((ViewGroup) findViewById(R.id.activity_main)).addView(messageTextView);
}
}
mHelloWorldPresenter != null
Moxy
Moxy
Задача #2
• Приложение запускается
• Проходит 5 секунд
• На экране отображается количество оставшихся секунд
• Отображается сообщение
Moxy
@InjectViewState
public class HelloWorldPresenter extends MvpPresenter<HelloWorldView> {
...
@Override protected void onPreExecute() {
getViewState().showTimer();
}
@Override protected Void doInBackground(Void... voids) {
for (int i = 5; i > 0; i--) {
publishProgress(i);
sleepSecond();
}
return null;
}
@Override protected void onProgressUpdate(Integer... values) {
getViewState().setTimer(values[0]);
}
@Override protected void onPostExecute(Void aVoid) {
getViewState().hideTimer();
getViewState().showMessage(R.string.hello_world);
}
...
Moxy
public interface HelloWorldView extends MvpView {
void showTimer();
void hideTimer();
void setTimer(int seconds);
void showMessage(int message);
}
Moxy
public class MainActivity extends MvpAppCompatActivity implements HelloWorldView {
...
private TextView mTimerTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mTimerTextView = (TextView) findViewById(R.id.timer_text_view);
}
@Override
public void showTimer() {
mTimerTextView.setVisibility(View.VISIBLE);
}
@Override
public void hideTimer() {
mTimerTextView.setVisibility(View.GONE);
}
@Override
public void setTimer(int seconds) {
mTimerTextView.setText(getString(R.string.timer, seconds));
}
...
Moxy
Moxy
Задача #3
• Приложение запускается
• Проходит 2 секунды
• На экране отображается количество оставшихся секунд
• Отображается сообщение в виде AlertDialog
Moxy
public class MainActivity extends MvpAppCompatActivity implements HelloWorldView {
...
private AlertDialog mMessageDialog;
@Override
public void showMessage(int message) {
mMessageDialog = new AlertDialog.Builder(this)
.setTitle(R.string.app_name).setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.setOnDismissListener(dialogInterface -> mHelloWorldPresenter.onDismissMessage())
.show();
}
@Override
public void hideMessage() {
if (mMessageDialog != null) {
mMessageDialog.dismiss();
}
}
}
Moxy
@InjectViewState
public class HelloWorldPresenter extends MvpPresenter<HelloWorldView> {
...
public void onDismissMessage() {
getViewState().hideMessage();
}
}
@StateStrategyType(AddToEndSingleStrategy.class)
public interface HelloWorldView extends MvpView {
void showTimer();
void hideTimer();
void setTimer(int seconds);
void showMessage(int message);
void hideMessage();
}
Moxy
Moxy
Задача #4
• Приложение запускается
• Проходит 5 секунд
• На экране отображается количество оставшихся секунд
• Отображается сообщение в виде сторонней View
Moxy
public class MainActivity extends MvpAppCompatActivity implements HelloWorldView {
...
private View mMessageView;
@Override
public void showMessage(int message) {
ViewGroup rootView = (ViewGroup) findViewById(R.id.activity_main);
mMessageView = LayoutInflater.from(this).inflate(R.layout.item_message, rootView, false);
rootView.addView(mMessageView);
((TextView) mMessageView.findViewById(R.id.message_text_view)).setText(message);
mMessageView.findViewById(R.id.close_button)
.setOnClickListener(v -> mHelloWorldPresenter.onDismissMessage());
}
@Override
public void hideMessage() {
ViewGroup rootView = (ViewGroup) findViewById(R.id.activity_main);
rootView.removeView(mMessageView);
}
}
Moxy
Moxy
Задача #5
• В Activity отображается 2 Fragment
• Каждый Fragment содержит счётчик нажатий на кнопку
• Изменения показаний счётчика одного фрагмента никак не
влияют на показания счётчика другого фрагмента
Moxy
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction
.add(R.id.frame_1, getFragment(0xffFF80AB))
.add(R.id.frame_2, getFragment(0xffCCFF90))
.commit();
}
}
private Fragment getFragment(int color) {
CounterFragment fragment = new CounterFragment();
Bundle args = new Bundle();
args.putInt("argColor", color);
fragment.setArguments(args);
return fragment;
}
}
Moxy
@InjectViewState
public class CounterPresenter extends MvpPresenter<CounterView> {
private int mCount;
public CounterPresenter() {
getViewState().showCount(mCount);
}
public void onPlusClick() {
mCount++;
getViewState().showCount(mCount);
}
}
public interface CounterView extends MvpView {
@StateStrategyType(AddToEndSingleStrategy.class)
void showCount(int count);
}
Moxy
public class CounterFragment extends MvpAppCompatFragment implements CounterView {
@InjectPresenter
CounterPresenter mCounterPresenter;
private TextView mCounterTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
return inflater.inflate(R.layout.fragment_counter, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
view.setBackgroundColor(getArguments().getInt("argColor"));
mCounterTextView = (TextView) getView().findViewById(R.id.count_text);
view.findViewById(R.id.plus_button).setOnClickListener(v -> mCounterPresenter.onPlusClick());
}
@Override
public void showCount(int count) {
mCounterTextView.setText(String.valueOf(count));
}
}
Moxy
Moxy
Задача #6
• В Activity отображается 2 Fragment
• Каждый Fragment содержит счётчик нажатий на кнопку
• Показания счётчиков синхронизированы
Moxy
public class CounterFragment extends MvpAppCompatFragment implements CounterView {
@InjectPresenter(type = PresenterType.GLOBAL, tag = "counterPresenter")
CounterPresenter mCounterPresenter;
...
Moxy
Moxy
Задача #7
• В Activity отображается 2 счётчика
• Каждый счётчик является Custom View
• Изменения показаний одного счётчика никак не влияют на
показания другого счётчика
Moxy
public class MvpActivity extends Activity {
private MvpDelegate<? extends MvpActivity> mMvpDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getMvpDelegate().onCreate(savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
getMvpDelegate().onAttach();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getMvpDelegate().onSaveInstanceState(outState);
}
...
Moxy
public class MainActivity extends MvpAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
((CounterWidget) findViewById(R.id.counter_1)).init(getMvpDelegate());
((CounterWidget) findViewById(R.id.counter_2)).init(getMvpDelegate());
}
}
Moxy
public class CounterWidget extends FrameLayout implements CounterView {
private MvpDelegate<CounterWidget> mMvpDelegate;
@InjectPresenter
CounterPresenter mCounterPresenter;
public void init(MvpDelegate parentDelegate) {
initMvpDelegate(parentDelegate);
mMvpDelegate.onCreate();
mMvpDelegate.onAttach();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mMvpDelegate.onSaveInstanceState();
mMvpDelegate.onDetach();
}
public void initMvpDelegate() {
mMvpDelegate = new MvpDelegate<>(this);
mMvpDelegate.setParentDelegate(mParentDelegate, String.valueOf(getId()));
}
private TextView mCounterTextView;
public CounterWidget(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.item_counter, this, true);
mCounterTextView = (TextView) findViewById(R.id.count_text);
View button = findViewById(R.id.plus_button);
button.setOnClickListener(view -> mCounterPresenter.onPlusClick());
}
@Override
public void showCount(int count) {
mCounterTextView.setText(String.valueOf(count));
}
Moxy
Moxy
Задача #8
Сделать экран деталей конкретной новости
Moxy
@InjectViewState
public class DetailsPresenter extends MvpPresenter<DetailsView> {
public DetailsPresenter(long newsId) {
loadNews(newsId);
}
private void loadNews(long newsId) {
getViewState().showDetails("Details of "" + newsId + """);
}
}
public interface DetailsView extends MvpView {
void showDetails(String details);
}
Moxy
public class DetailsActivity extends MvpAppCompatActivity implements DetailsView {
@InjectPresenter
DetailsPresenter mDetailsPresenter;
@ProvidePresenter
DetailsPresenter provideDetailsPresenter() {
return new DetailsPresenter(getIntent().getLongExtra("extraDetailsId", 0));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
}
@Override
public void showDetails(String details) {
Log.i(DetailsActivity.class.getSimpleName(), details);
}
}
equals
equals
Moxy
Задача #9
• Сделать экран деталей конкретной новости
• Для каждой новости – свой глобальный Presenter
Moxy
public class DetailsActivity extends MvpAppCompatActivity implements DetailsView {
@InjectPresenter(type = PresenterType.GLOBAL)
DetailsPresenter mDetailsPresenter;
@ProvidePresenterTag(presenterClass = DetailsPresenter.class, type = PresenterType.GLOBAL)
String provideDetailsPresenterTag() {
return "details_" + getIntent().getLongExtra("extraDetailsId", 0);
}
@ProvidePresenter(type = PresenterType.GLOBAL)
DetailsPresenter provideDetailsPresenter() {
return new DetailsPresenter(getIntent().getLongExtra("extraDetailsId", 0));
}
...
equals equals
exclude tag
Moxy
Принцип действия. #1. @InjectPresenter
0. Annotation processor: Generate PresenterFields
1. MvpDelegate: onCreate(savedInstanceState)
2. MvpDelegate: Init delegate tag
3. MvpProcessor: Collect all PresenterField for MvpDelegate
4. MvpProcessor: Init each PresenterField
1. MvpProcessor: Generate presenter tag
2. PresenterStore: Get MvpPresenter by type and tag
3. MvpProcessor: MvpPresenter exists?
1. True:
1. MvpProcessor: Init presenter field of Delegated
2. False:
1. PresenterField: Provide presenter
2. PresenterStore: Save presenter
3. MvpProcessor: Init presenter field of Delegated
Moxy
Принцип действия. #2. @InjectViewState
0. Annotation processor: Generate ViewState
1. MvpPresenter: Construct
2. Binder: Bind presenter
3. Binder: Find ViewState for MvpPresenter
4. Binder: Create ViewState
5. Binder: Set ViewState to MvpPresenter
Moxy
Принцип действия. #3. ViewState
1. MvpPresenter: Send command
2. ViewState: Instantiation of ViewCommand
3. ViewState: Get StateStrategy of ViewCommand
4. StateStrategy: Called beforeApply(currentState, incomingCommand)
5. ViewState: Have a Views?
1. False: –
2. True:
1. ViewCommand: Apply to each Views
2. StateStrategy: Called afterApply(currentState, incomingState)
6. ViewState: Attached View
7. ViewState: Apply each ViewCommands
1. ViewCommand: Apply to attached View
2. StateStrategy: Called afterApply(currentState, incomingState)
Moxy
Итого
1. Нет проблем с жизненным циклом
2. Boilerplate-code генерируется в compile time
3. Можно использовать несколько Presenter в одном месте
4. Можно любой компонент превратить в MvpView
5. Можно использовать один экземпляр MvpPresenter в нескольких местах
Присоединяйтесь к проекту на github.com!
На CodeLab сделаем небольшой Github-client ;) + посмотрим результат кодогенерации
PS: https://github.com/senneco/MoxyCases
THANK YOU!
#DevFest16 #dfSiberia #GDGNsk #GDGOmsk

More Related Content

What's hot

Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, ControllersШкола-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Глеб Тарасов
 
Курсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOS
Курсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOSКурсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOS
Курсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOS
Глеб Тарасов
 
Библиотека Google Closure Java Script
Библиотека Google Closure Java ScriptБиблиотека Google Closure Java Script
Библиотека Google Closure Java Script
Volodymyr Forfutdinov
 
Fragments (Lecture 09 – Fragments)
Fragments (Lecture 09 – Fragments)Fragments (Lecture 09 – Fragments)
Fragments (Lecture 09 – Fragments)
Noveo
 

What's hot (20)

Модульная структура
Модульная структураМодульная структура
Модульная структура
 
Как приручить реактивное программирование в XAML приложениях
Как приручить реактивное программирование в XAML приложенияхКак приручить реактивное программирование в XAML приложениях
Как приручить реактивное программирование в XAML приложениях
 
Как приручить реактивное программирование
Как приручить реактивное программированиеКак приручить реактивное программирование
Как приручить реактивное программирование
 
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, ControllersШкола-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
 
Олексій Стульніков “WinAppDriver – автоматизація Desktop ніколи не була такою...
Олексій Стульніков “WinAppDriver – автоматизація Desktop ніколи не була такою...Олексій Стульніков “WinAppDriver – автоматизація Desktop ніколи не була такою...
Олексій Стульніков “WinAppDriver – автоматизація Desktop ніколи не була такою...
 
C# Web. Занятие 11.
C# Web. Занятие 11.C# Web. Занятие 11.
C# Web. Занятие 11.
 
AndroidMVPHelper
AndroidMVPHelperAndroidMVPHelper
AndroidMVPHelper
 
Dependency Injection на примере Unity и NInject
Dependency Injection на примере Unity и NInjectDependency Injection на примере Unity и NInject
Dependency Injection на примере Unity и NInject
 
Курсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOS
Курсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOSКурсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOS
Курсы по мобильной разработке. 2 лекция. Построение интерфейсов в iOS
 
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
 
Build a VR Pawn with Unreal Engine Luis Cataldi Russian
Build a VR Pawn with Unreal Engine   Luis Cataldi RussianBuild a VR Pawn with Unreal Engine   Luis Cataldi Russian
Build a VR Pawn with Unreal Engine Luis Cataldi Russian
 
Алексей Андросов - Debugger: Отладка кода
Алексей Андросов - Debugger: Отладка кодаАлексей Андросов - Debugger: Отладка кода
Алексей Андросов - Debugger: Отладка кода
 
Автоматизация UI тестирования под Windows и Windows Phone
Автоматизация UI тестирования под Windows и Windows PhoneАвтоматизация UI тестирования под Windows и Windows Phone
Автоматизация UI тестирования под Windows и Windows Phone
 
JavaScript Базовый. Занятие 10.
JavaScript Базовый. Занятие 10.JavaScript Базовый. Занятие 10.
JavaScript Базовый. Занятие 10.
 
Создание графического интерфейса пользователя мобильных Android приложений (ч...
Создание графического интерфейса пользователя мобильных Android приложений (ч...Создание графического интерфейса пользователя мобильных Android приложений (ч...
Создание графического интерфейса пользователя мобильных Android приложений (ч...
 
55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)
55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)
55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)
 
JavaScript Базовый. Занятие 09.
JavaScript Базовый. Занятие 09.JavaScript Базовый. Занятие 09.
JavaScript Базовый. Занятие 09.
 
Библиотека Google Closure Java Script
Библиотека Google Closure Java ScriptБиблиотека Google Closure Java Script
Библиотека Google Closure Java Script
 
Лекция Android. БД SQLite, ContentProvider, Loader
Лекция Android. БД SQLite, ContentProvider, LoaderЛекция Android. БД SQLite, ContentProvider, Loader
Лекция Android. БД SQLite, ContentProvider, Loader
 
Fragments (Lecture 09 – Fragments)
Fragments (Lecture 09 – Fragments)Fragments (Lecture 09 – Fragments)
Fragments (Lecture 09 – Fragments)
 

Similar to MVP, Moxy. Как правильно пользоваться

Mobile automation uamobile
Mobile automation uamobileMobile automation uamobile
Mobile automation uamobile
UA Mobile
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Google
yaevents
 
Windows Azure & NodeJS Microsoft SWIT 2012
Windows Azure & NodeJS Microsoft SWIT 2012 Windows Azure & NodeJS Microsoft SWIT 2012
Windows Azure & NodeJS Microsoft SWIT 2012
Dmytro Mindra
 

Similar to MVP, Moxy. Как правильно пользоваться (20)

iOS and Android Mobile Test Automation
iOS and Android Mobile Test AutomationiOS and Android Mobile Test Automation
iOS and Android Mobile Test Automation
 
Mobile automation uamobile
Mobile automation uamobileMobile automation uamobile
Mobile automation uamobile
 
Разбираемся с CoordinatorLayout
Разбираемся с CoordinatorLayoutРазбираемся с CoordinatorLayout
Разбираемся с CoordinatorLayout
 
RoboGuice
RoboGuiceRoboGuice
RoboGuice
 
Android Development Course in HSE lecture #3
Android Development Course in HSE lecture #3Android Development Course in HSE lecture #3
Android Development Course in HSE lecture #3
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Google
 
Паттерны проектирования
Паттерны проектированияПаттерны проектирования
Паттерны проектирования
 
Android service
Android serviceAndroid service
Android service
 
Windows Azure & NodeJS Microsoft SWIT 2012
Windows Azure & NodeJS Microsoft SWIT 2012 Windows Azure & NodeJS Microsoft SWIT 2012
Windows Azure & NodeJS Microsoft SWIT 2012
 
Xamarin: кроссплатформенные грабли, Дмитрий Моисеев, СКБ Контур
 Xamarin: кроссплатформенные грабли, Дмитрий Моисеев, СКБ Контур  Xamarin: кроссплатформенные грабли, Дмитрий Моисеев, СКБ Контур
Xamarin: кроссплатформенные грабли, Дмитрий Моисеев, СКБ Контур
 
MVVM в WinForms – DevExpress Way (теория и практика)
MVVM в WinForms – DevExpress Way (теория и практика)MVVM в WinForms – DevExpress Way (теория и практика)
MVVM в WinForms – DevExpress Way (теория и практика)
 
MWWM
MWWMMWWM
MWWM
 
Views обзор
Views обзорViews обзор
Views обзор
 
Веселая ферма. Соседи.
Веселая ферма. Соседи.Веселая ферма. Соседи.
Веселая ферма. Соседи.
 
Всеволод Шмыров, Яндекс
Всеволод Шмыров, ЯндексВсеволод Шмыров, Яндекс
Всеволод Шмыров, Яндекс
 
HTML 5
HTML 5HTML 5
HTML 5
 
RxJava+RxAndroid (Lecture 20 – rx java)
RxJava+RxAndroid (Lecture 20 – rx java)RxJava+RxAndroid (Lecture 20 – rx java)
RxJava+RxAndroid (Lecture 20 – rx java)
 
МАИ, Сети ЭВМ, Лекция №5
МАИ, Сети ЭВМ, Лекция №5МАИ, Сети ЭВМ, Лекция №5
МАИ, Сети ЭВМ, Лекция №5
 
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft UkraineHTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
 
Gwt jug basic
Gwt jug basicGwt jug basic
Gwt jug basic
 

MVP, Moxy. Как правильно пользоваться

  • 1. А
  • 2. MVP, Moxy Как правильно пользоваться Yuri Shmakov Android Team leader Arello Mobile Russia
  • 3. Moxy Общий подход • Определить Presenter • Определить интерфейс View • Реализовать View • Выделить бизнес-логику в Model
  • 4. Moxy Требования к решениям задач • Автоматическое сохранение отображения при пересоздании View • Никакого boilerplate-кода • Отсутствие ненужных флажков, id, switch-case, e.t.c.
  • 5. Moxy Задача #1 • Приложение запускается • Проходит 1 секунда • Отображается сообщение
  • 6. Moxy public interface HelloWorldView extends MvpView { void showMessage(int message); }
  • 7. Moxy @InjectViewState public class HelloWorldPresenter extends MvpPresenter<HelloWorldView> { public HelloWorldPresenter() { AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { sleepSecond(); return null; } @Override protected void onPostExecute(Void aVoid) { getViewState().showMessage(R.string.hello_world); } private void sleepSecond() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ignore) {} } }; asyncTask.execute(); } }
  • 8. Moxy public class MainActivity extends MvpAppCompatActivity implements HelloWorldView { @InjectPresenter HelloWorldPresenter mHelloWorldPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void showMessage(int message) { TextView messageTextView = new TextView(this); messageTextView.setText(message); messageTextView.setTextSize(40); messageTextView.setGravity(Gravity.CENTER_HORIZONTAL); ((ViewGroup) findViewById(R.id.activity_main)).addView(messageTextView); } } mHelloWorldPresenter != null
  • 10. Moxy Задача #2 • Приложение запускается • Проходит 5 секунд • На экране отображается количество оставшихся секунд • Отображается сообщение
  • 11. Moxy @InjectViewState public class HelloWorldPresenter extends MvpPresenter<HelloWorldView> { ... @Override protected void onPreExecute() { getViewState().showTimer(); } @Override protected Void doInBackground(Void... voids) { for (int i = 5; i > 0; i--) { publishProgress(i); sleepSecond(); } return null; } @Override protected void onProgressUpdate(Integer... values) { getViewState().setTimer(values[0]); } @Override protected void onPostExecute(Void aVoid) { getViewState().hideTimer(); getViewState().showMessage(R.string.hello_world); } ...
  • 12. Moxy public interface HelloWorldView extends MvpView { void showTimer(); void hideTimer(); void setTimer(int seconds); void showMessage(int message); }
  • 13. Moxy public class MainActivity extends MvpAppCompatActivity implements HelloWorldView { ... private TextView mTimerTextView; @Override protected void onCreate(Bundle savedInstanceState) { ... mTimerTextView = (TextView) findViewById(R.id.timer_text_view); } @Override public void showTimer() { mTimerTextView.setVisibility(View.VISIBLE); } @Override public void hideTimer() { mTimerTextView.setVisibility(View.GONE); } @Override public void setTimer(int seconds) { mTimerTextView.setText(getString(R.string.timer, seconds)); } ...
  • 14. Moxy
  • 15. Moxy Задача #3 • Приложение запускается • Проходит 2 секунды • На экране отображается количество оставшихся секунд • Отображается сообщение в виде AlertDialog
  • 16. Moxy public class MainActivity extends MvpAppCompatActivity implements HelloWorldView { ... private AlertDialog mMessageDialog; @Override public void showMessage(int message) { mMessageDialog = new AlertDialog.Builder(this) .setTitle(R.string.app_name).setMessage(message) .setPositiveButton(android.R.string.ok, null) .setOnDismissListener(dialogInterface -> mHelloWorldPresenter.onDismissMessage()) .show(); } @Override public void hideMessage() { if (mMessageDialog != null) { mMessageDialog.dismiss(); } } }
  • 17. Moxy @InjectViewState public class HelloWorldPresenter extends MvpPresenter<HelloWorldView> { ... public void onDismissMessage() { getViewState().hideMessage(); } } @StateStrategyType(AddToEndSingleStrategy.class) public interface HelloWorldView extends MvpView { void showTimer(); void hideTimer(); void setTimer(int seconds); void showMessage(int message); void hideMessage(); }
  • 18. Moxy
  • 19. Moxy Задача #4 • Приложение запускается • Проходит 5 секунд • На экране отображается количество оставшихся секунд • Отображается сообщение в виде сторонней View
  • 20. Moxy public class MainActivity extends MvpAppCompatActivity implements HelloWorldView { ... private View mMessageView; @Override public void showMessage(int message) { ViewGroup rootView = (ViewGroup) findViewById(R.id.activity_main); mMessageView = LayoutInflater.from(this).inflate(R.layout.item_message, rootView, false); rootView.addView(mMessageView); ((TextView) mMessageView.findViewById(R.id.message_text_view)).setText(message); mMessageView.findViewById(R.id.close_button) .setOnClickListener(v -> mHelloWorldPresenter.onDismissMessage()); } @Override public void hideMessage() { ViewGroup rootView = (ViewGroup) findViewById(R.id.activity_main); rootView.removeView(mMessageView); } }
  • 21. Moxy
  • 22. Moxy Задача #5 • В Activity отображается 2 Fragment • Каждый Fragment содержит счётчик нажатий на кнопку • Изменения показаний счётчика одного фрагмента никак не влияют на показания счётчика другого фрагмента
  • 23. Moxy public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); if (savedInstanceState == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction .add(R.id.frame_1, getFragment(0xffFF80AB)) .add(R.id.frame_2, getFragment(0xffCCFF90)) .commit(); } } private Fragment getFragment(int color) { CounterFragment fragment = new CounterFragment(); Bundle args = new Bundle(); args.putInt("argColor", color); fragment.setArguments(args); return fragment; } }
  • 24. Moxy @InjectViewState public class CounterPresenter extends MvpPresenter<CounterView> { private int mCount; public CounterPresenter() { getViewState().showCount(mCount); } public void onPlusClick() { mCount++; getViewState().showCount(mCount); } } public interface CounterView extends MvpView { @StateStrategyType(AddToEndSingleStrategy.class) void showCount(int count); }
  • 25. Moxy public class CounterFragment extends MvpAppCompatFragment implements CounterView { @InjectPresenter CounterPresenter mCounterPresenter; private TextView mCounterTextView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { return inflater.inflate(R.layout.fragment_counter, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { view.setBackgroundColor(getArguments().getInt("argColor")); mCounterTextView = (TextView) getView().findViewById(R.id.count_text); view.findViewById(R.id.plus_button).setOnClickListener(v -> mCounterPresenter.onPlusClick()); } @Override public void showCount(int count) { mCounterTextView.setText(String.valueOf(count)); } }
  • 26. Moxy
  • 27. Moxy Задача #6 • В Activity отображается 2 Fragment • Каждый Fragment содержит счётчик нажатий на кнопку • Показания счётчиков синхронизированы
  • 28. Moxy public class CounterFragment extends MvpAppCompatFragment implements CounterView { @InjectPresenter(type = PresenterType.GLOBAL, tag = "counterPresenter") CounterPresenter mCounterPresenter; ...
  • 29. Moxy
  • 30. Moxy Задача #7 • В Activity отображается 2 счётчика • Каждый счётчик является Custom View • Изменения показаний одного счётчика никак не влияют на показания другого счётчика
  • 31. Moxy public class MvpActivity extends Activity { private MvpDelegate<? extends MvpActivity> mMvpDelegate; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getMvpDelegate().onCreate(savedInstanceState); } @Override protected void onStart() { super.onStart(); getMvpDelegate().onAttach(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); getMvpDelegate().onSaveInstanceState(outState); } ...
  • 32. Moxy public class MainActivity extends MvpAppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main3); ((CounterWidget) findViewById(R.id.counter_1)).init(getMvpDelegate()); ((CounterWidget) findViewById(R.id.counter_2)).init(getMvpDelegate()); } }
  • 33. Moxy public class CounterWidget extends FrameLayout implements CounterView { private MvpDelegate<CounterWidget> mMvpDelegate; @InjectPresenter CounterPresenter mCounterPresenter; public void init(MvpDelegate parentDelegate) { initMvpDelegate(parentDelegate); mMvpDelegate.onCreate(); mMvpDelegate.onAttach(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mMvpDelegate.onSaveInstanceState(); mMvpDelegate.onDetach(); } public void initMvpDelegate() { mMvpDelegate = new MvpDelegate<>(this); mMvpDelegate.setParentDelegate(mParentDelegate, String.valueOf(getId())); } private TextView mCounterTextView; public CounterWidget(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.item_counter, this, true); mCounterTextView = (TextView) findViewById(R.id.count_text); View button = findViewById(R.id.plus_button); button.setOnClickListener(view -> mCounterPresenter.onPlusClick()); } @Override public void showCount(int count) { mCounterTextView.setText(String.valueOf(count)); }
  • 34. Moxy
  • 35. Moxy Задача #8 Сделать экран деталей конкретной новости
  • 36. Moxy @InjectViewState public class DetailsPresenter extends MvpPresenter<DetailsView> { public DetailsPresenter(long newsId) { loadNews(newsId); } private void loadNews(long newsId) { getViewState().showDetails("Details of "" + newsId + """); } } public interface DetailsView extends MvpView { void showDetails(String details); }
  • 37. Moxy public class DetailsActivity extends MvpAppCompatActivity implements DetailsView { @InjectPresenter DetailsPresenter mDetailsPresenter; @ProvidePresenter DetailsPresenter provideDetailsPresenter() { return new DetailsPresenter(getIntent().getLongExtra("extraDetailsId", 0)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_details); } @Override public void showDetails(String details) { Log.i(DetailsActivity.class.getSimpleName(), details); } } equals equals
  • 38. Moxy Задача #9 • Сделать экран деталей конкретной новости • Для каждой новости – свой глобальный Presenter
  • 39. Moxy public class DetailsActivity extends MvpAppCompatActivity implements DetailsView { @InjectPresenter(type = PresenterType.GLOBAL) DetailsPresenter mDetailsPresenter; @ProvidePresenterTag(presenterClass = DetailsPresenter.class, type = PresenterType.GLOBAL) String provideDetailsPresenterTag() { return "details_" + getIntent().getLongExtra("extraDetailsId", 0); } @ProvidePresenter(type = PresenterType.GLOBAL) DetailsPresenter provideDetailsPresenter() { return new DetailsPresenter(getIntent().getLongExtra("extraDetailsId", 0)); } ... equals equals exclude tag
  • 40. Moxy Принцип действия. #1. @InjectPresenter 0. Annotation processor: Generate PresenterFields 1. MvpDelegate: onCreate(savedInstanceState) 2. MvpDelegate: Init delegate tag 3. MvpProcessor: Collect all PresenterField for MvpDelegate 4. MvpProcessor: Init each PresenterField 1. MvpProcessor: Generate presenter tag 2. PresenterStore: Get MvpPresenter by type and tag 3. MvpProcessor: MvpPresenter exists? 1. True: 1. MvpProcessor: Init presenter field of Delegated 2. False: 1. PresenterField: Provide presenter 2. PresenterStore: Save presenter 3. MvpProcessor: Init presenter field of Delegated
  • 41. Moxy Принцип действия. #2. @InjectViewState 0. Annotation processor: Generate ViewState 1. MvpPresenter: Construct 2. Binder: Bind presenter 3. Binder: Find ViewState for MvpPresenter 4. Binder: Create ViewState 5. Binder: Set ViewState to MvpPresenter
  • 42. Moxy Принцип действия. #3. ViewState 1. MvpPresenter: Send command 2. ViewState: Instantiation of ViewCommand 3. ViewState: Get StateStrategy of ViewCommand 4. StateStrategy: Called beforeApply(currentState, incomingCommand) 5. ViewState: Have a Views? 1. False: – 2. True: 1. ViewCommand: Apply to each Views 2. StateStrategy: Called afterApply(currentState, incomingState) 6. ViewState: Attached View 7. ViewState: Apply each ViewCommands 1. ViewCommand: Apply to attached View 2. StateStrategy: Called afterApply(currentState, incomingState)
  • 43. Moxy Итого 1. Нет проблем с жизненным циклом 2. Boilerplate-code генерируется в compile time 3. Можно использовать несколько Presenter в одном месте 4. Можно любой компонент превратить в MvpView 5. Можно использовать один экземпляр MvpPresenter в нескольких местах Присоединяйтесь к проекту на github.com! На CodeLab сделаем небольшой Github-client ;) + посмотрим результат кодогенерации PS: https://github.com/senneco/MoxyCases