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

44
А

Upload: yuri-shmakov

Post on 20-Mar-2017

744 views

Category:

Software


0 download

TRANSCRIPT

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

А

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

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

Yuri ShmakovAndroid Team leader

Arello MobileRussia

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

MoxyОбщий подход

• Определить Presenter• Определить интерфейс View• Реализовать View• Выделить бизнес-логику в Model

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

MoxyТребования к решениям задач

• Автоматическое сохранение отображения при пересоздании View• Никакого boilerplate-кода• Отсутствие ненужных флажков, id, switch-case, e.t.c.

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

MoxyЗадача #1

• Приложение запускается• Проходит 1 секунда• Отображается сообщение

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

Moxypublic interface HelloWorldView extends MvpView { void showMessage(int message);}

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

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

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

Moxypublic 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

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

Moxy

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

MoxyЗадача #2

• Приложение запускается• Проходит 5 секунд• На экране отображается количество оставшихся секунд

• Отображается сообщение

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

Moxy@InjectViewStatepublic 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); } ...

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

Moxypublic interface HelloWorldView extends MvpView { void showTimer();

void hideTimer();

void setTimer(int seconds);

void showMessage(int message);}

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

Moxypublic 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)); } ...

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

Moxy

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

MoxyЗадача #3

• Приложение запускается• Проходит 2 секунды• На экране отображается количество оставшихся секунд

• Отображается сообщение в виде AlertDialog

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

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

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

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

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

Moxy

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

MoxyЗадача #4

• Приложение запускается• Проходит 5 секунд• На экране отображается количество оставшихся секунд

• Отображается сообщение в виде сторонней View

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

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

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

Moxy

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

MoxyЗадача #5

• В Activity отображается 2 Fragment• Каждый Fragment содержит счётчик нажатий на кнопку• Изменения показаний счётчика одного фрагмента никак не

влияют на показания счётчика другого фрагмента

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

Moxypublic 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; }}

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

Moxy@InjectViewStatepublic 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);}

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

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

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

Moxy

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

MoxyЗадача #6

• В Activity отображается 2 Fragment• Каждый Fragment содержит счётчик нажатий на кнопку• Показания счётчиков синхронизированы

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

Moxypublic class CounterFragment extends MvpAppCompatFragment implements CounterView {

@InjectPresenter(type = PresenterType.GLOBAL, tag = "counterPresenter") CounterPresenter mCounterPresenter; ...

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

Moxy

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

MoxyЗадача #7

• В Activity отображается 2 счётчика• Каждый счётчик является Custom View• Изменения показаний одного счётчика никак не влияют на

показания другого счётчика

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

Moxypublic 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); } ...

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

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

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

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

@Overridepublic void showCount(int count) { mCounterTextView.setText(String.valueOf(count));}

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

Moxy

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

MoxyЗадача #8

Сделать экран деталей конкретной новости

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

Moxy

@InjectViewStatepublic 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);}

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

Moxypublic 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

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

MoxyЗадача #9

• Сделать экран деталей конкретной новости• Для каждой новости – свой глобальный Presenter

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

Moxypublic 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 equalsexclude tag

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

MoxyПринцип действия. #1. @InjectPresenter

0. Annotation processor: Generate PresenterFields1. MvpDelegate: onCreate(savedInstanceState)2. MvpDelegate: Init delegate tag3. MvpProcessor: Collect all PresenterField for MvpDelegate4. MvpProcessor: Init each PresenterField

1. MvpProcessor: Generate presenter tag2. PresenterStore: Get MvpPresenter by type and tag3. MvpProcessor: MvpPresenter exists?

1. True:1. MvpProcessor: Init presenter field of Delegated

2. False:1. PresenterField: Provide presenter2. PresenterStore: Save presenter3. MvpProcessor: Init presenter field of Delegated

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

MoxyПринцип действия. #2. @InjectViewState

0. Annotation processor: Generate ViewState1. MvpPresenter: Construct2. Binder: Bind presenter3. Binder: Find ViewState for MvpPresenter4. Binder: Create ViewState5. Binder: Set ViewState to MvpPresenter

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

MoxyПринцип действия. #3. ViewState

1. MvpPresenter: Send command2. ViewState: Instantiation of ViewCommand3. ViewState: Get StateStrategy of ViewCommand4. StateStrategy: Called beforeApply(currentState, incomingCommand)5. ViewState: Have a Views?

1. False: –2. True:

1. ViewCommand: Apply to each Views2. StateStrategy: Called afterApply(currentState, incomingState)

6. ViewState: Attached View7. ViewState: Apply each ViewCommands

1. ViewCommand: Apply to attached View2. StateStrategy: Called afterApply(currentState, incomingState)

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

MoxyИтого

1. Нет проблем с жизненным циклом2. Boilerplate-code генерируется в compile time3. Можно использовать несколько Presenter в одном месте4. Можно любой компонент превратить в MvpView5. Можно использовать один экземпляр MvpPresenter в нескольких местах

Присоединяйтесь к проекту на github.com!

На CodeLab сделаем небольшой Github-client ;) + посмотрим результат кодогенерации

PS: https://github.com/senneco/MoxyCases

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

THANK YOU!

#DevFest16 #dfSiberia #GDGNsk #GDGOmsk