MVP, Moxy. Как правильно пользоваться
-
Upload
yuri-shmakov -
Category
Software
-
view
722 -
download
0
Transcript of MVP, Moxy. Как правильно пользоваться
А
MVP, MoxyКак правильно пользоваться
Yuri ShmakovAndroid Team leader
Arello MobileRussia
MoxyОбщий подход
• Определить Presenter• Определить интерфейс View• Реализовать View• Выделить бизнес-логику в Model
MoxyТребования к решениям задач
• Автоматическое сохранение отображения при пересоздании View• Никакого boilerplate-кода• Отсутствие ненужных флажков, id, switch-case, e.t.c.
MoxyЗадача #1
• Приложение запускается• Проходит 1 секунда• Отображается сообщение
Moxypublic interface HelloWorldView extends MvpView { void showMessage(int message);}
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(); }}
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
Moxy
MoxyЗадача #2
• Приложение запускается• Проходит 5 секунд• На экране отображается количество оставшихся секунд
• Отображается сообщение
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); } ...
Moxypublic interface HelloWorldView extends MvpView { void showTimer();
void hideTimer();
void setTimer(int seconds);
void showMessage(int message);}
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)); } ...
Moxy
MoxyЗадача #3
• Приложение запускается• Проходит 2 секунды• На экране отображается количество оставшихся секунд
• Отображается сообщение в виде AlertDialog
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(); } }}
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();}
Moxy
MoxyЗадача #4
• Приложение запускается• Проходит 5 секунд• На экране отображается количество оставшихся секунд
• Отображается сообщение в виде сторонней View
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); }}
Moxy
MoxyЗадача #5
• В Activity отображается 2 Fragment• Каждый Fragment содержит счётчик нажатий на кнопку• Изменения показаний счётчика одного фрагмента никак не
влияют на показания счётчика другого фрагмента
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; }}
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);}
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)); }}
Moxy
MoxyЗадача #6
• В Activity отображается 2 Fragment• Каждый Fragment содержит счётчик нажатий на кнопку• Показания счётчиков синхронизированы
Moxypublic class CounterFragment extends MvpAppCompatFragment implements CounterView {
@InjectPresenter(type = PresenterType.GLOBAL, tag = "counterPresenter") CounterPresenter mCounterPresenter; ...
Moxy
MoxyЗадача #7
• В Activity отображается 2 счётчика• Каждый счётчик является Custom View• Изменения показаний одного счётчика никак не влияют на
показания другого счётчика
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); } ...
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()); }}
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));}
Moxy
MoxyЗадача #8
Сделать экран деталей конкретной новости
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);}
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
MoxyЗадача #9
• Сделать экран деталей конкретной новости• Для каждой новости – свой глобальный Presenter
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
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
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
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)
MoxyИтого
1. Нет проблем с жизненным циклом2. Boilerplate-code генерируется в compile time3. Можно использовать несколько Presenter в одном месте4. Можно любой компонент превратить в MvpView5. Можно использовать один экземпляр MvpPresenter в нескольких местах
Присоединяйтесь к проекту на github.com!
На CodeLab сделаем небольшой Github-client ;) + посмотрим результат кодогенерации
PS: https://github.com/senneco/MoxyCases
THANK YOU!
#DevFest16 #dfSiberia #GDGNsk #GDGOmsk