Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ThirtyInch - Living next to the Activity

Pascal Welsch
November 24, 2016

ThirtyInch - Living next to the Activity

Explains when you should use ThirtyInch and when you should use MVP at all. It also covers the different kinds of MVP, statless and stateful.
The code sample focuses on MVVM, or as I call it VPVM (view presenter viewmodel).

Project on github: https://github.com/grandcentrix/ThirtyInch
Sample on github: https://github.com/passsy/thirtyinch-sample

Pascal Welsch

November 24, 2016
Tweet

More Decks by Pascal Welsch

Other Decks in Technology

Transcript

  1. Why is MVP a thing on Android? Moving all logic

    to a Presenter makes the code testable ◦ Only self written code ◦ Easy lifecycle, view attached/detached ◦ Unit tests on JVM Activities and Fragments are hard to test ◦ Have final methods and classes ◦ Constructor dependency injection not possible ◦ Requires emulator to run androidTest tests (slow) ◦ The lifecycle is hard to mock and too complex
  2. “What if I really don’t write tests?” • MVP introduces

    new boilerplate code • There are some parts which will be more complex than before • You most likely will not benefit from MVP Try testing with MVP, it will be easier than ever before! Don’t use MVP
  3. MVP != MVP Stateless Presenter will be created every time

    in Activity#onCreate Doesn’t solve the problem or network requests when the phone is changing orientation Hold state (model) in the Activity and save is in onSaveInstanceState() Stateful Presenter survives orientation changes Network requests keep running when the phone is changing orientation Hold state objects (model) in the Presenter
  4. Stateless Presenter • Created in onCreate() • View attached in

    constructor • New Presenter object when the Activity recreates • Move state to new Activity instance with onSaveInstanceState()
  5. Stateless Presenter • Created in onCreate() • View attached when

    displayed • View detached when the app in in background • New Presenter object when the Activity recreates • Move state to new Activity instance with onSaveInstanceState()
  6. Stateful Presenter Created in onCreate() when savedInstanceState == null Destroy

    callback when Activity gets finished in onDestroy() with isFinishing() == true When the Activity gets recreated the Presenter does survives!
  7. Stateful Presenter The state (model) of the Activity can be

    saved in the Presenter (in memory) instead of using Parcelables Ignores all lifecycle events except for the availability of the View Network requests can run during a orientation change and update the model, not the view Implementing a stateful Presenter works best with a ViewModel and a passive view (VPVM) Living next to the Activity
  8. 1. Define View interface interface EditTaskView extends TiView { void

    setTitle(String title); void showLoadingIndicator(boolean show); }
  9. 3. Activity#providePresenter() public class EditTaskActivity extends TiActivity<EditTaskPresenter, EditTaskView> implements EditTaskView

    { @NonNull @Override public AddEditTaskPresenter providePresenter() { final String taskId = getIntent().getStringExtra(EditTaskActivity.ARGUMENT_EDIT_TASK_ID); final TasksRepository tasksRepository = Injection.provideTasksRepository(this); return new EditTaskPresenter(taskId, tasksRepository); }
  10. 4. Activity implements EditTaskView @Override public void setTitle(String title) {

    mTitle.setText(title); } @Override public void showLoadingIndicator(final boolean show) { if (show) { mLoadingIndicator.show(); } else { mLoadingIndicator.hide(); } }
  11. 5. send UI input events public class EditTaskActivity extends TiActivity<EditTaskPresenter,

    EditTaskView> implements EditTaskView { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.addtask_act); mFab = (FloatingActionButton) findViewById(R.id.fab_edit_task_done); mFab.setOnClickListener(view -> { getPresenter().saveTask(); });
  12. Presenter constructor class EditTaskPresenter extends TiPresenter<EditTaskView> { EditTaskPresenter(@Nullable String taskId,

    @NonNull TasksDataSource tasksRepository) { mTaskId = taskId; mTasksRepository = checkNotNull(tasksRepository); mViewModel = new EditTaskViewModel(); }
  13. Presenter#onCreate() @Override protected void onCreate() { super.onCreate(); mViewModel.setLoadingTask(true); mRepositoryRequest =

    mTasksRepository.getTask(mTaskId, task -> { mViewModel.setLoadingTask(false); mViewModel.setTask(task); }); }
  14. Presenter#onAttachView(view) @Override protected void onAttachView(@NonNull final EditTaskView view) { super.onAttachView(view);

    mViewModel.setOnChangeListener(model -> { view.setTitle(model.getTitle()); view.showLoadingIndicator(model.isLoading()); }); }
  15. ViewModel properties class EditTaskViewModel { private Task mTask = null;

    private boolean mIsLoadingTask = false; private NotifyChangeListener<EditTaskViewModel> mNotifyChangeListener; private void notifyChange() { if (mNotifyChangeListener != null) { mNotifyChangeListener.onChange(this); } } ...
  16. ViewModel notifyChange() class EditTaskViewModel { ... public boolean isLoading() {

    return mIsLoadingTask; } public void setLoadingTask(final boolean loadingTask) { if (mIsLoadingTask != loadingTask) { mIsLoadingTask = loadingTask; notifyChange(); } }
  17. ViewModel is the truth Update the ViewModel (not view) when

    - data changes - receiving UI events Update the View when - the View attaches - The ViewModel changes This prevents NPEs when View == null (detached) Always update everything (React way)
  18. DistinctUntilChanged mViewModel.setOnChangeListener(model -> { view.setTitle(model.getTitle()); view.showLoadingIndicator(model.isLoading()); }); interface EditTaskView extends

    TiView { @DistinctUntilChanged void showLoadingIndicator(boolean show); } @DistinctUntilChanged Prevents calling methods with the same arguments over and over again
  19. Modules def tiVersion = '0.8.0-rc1' compile "net.grandcentrix.thirtyinch:thirtyinch:$tiVersion" // rx extension

    compile "net.grandcentrix.thirtyinch:thirtyinch-rx:$tiVersion" // test extension testCompile "net.grandcentrix.thirtyinch:thirtyinch-test:$tiVersion"
  20. Testing on JVM @Test public void populateTask_callsRepoAndUpdatesView() { // Get

    a reference to the class under test final EditTaskPresenter presenter = new EditTaskPresenter("1", mTasksRepository); final TiPresenterInstructor<EditTaskView> instructor = new TiPresenterInstructor<>(presenter); // When task is requested and view is attached final AddEditTaskView view = mock(AddEditTaskView.class); instructor.attachView(view);
  21. Testing // When task is requested and view is attached

    final AddEditTaskView view = mock(AddEditTaskView.class); instructor.attachView(view); verify(view).setTitle(""); verify(view).showLoadingIndicator(true);
  22. // get fake task from repository final ArgumentCaptor<TasksDataSource.GetTaskCallback> captor =

    ArgumentCaptor.forClass(TasksDataSource.GetTaskCallback.class); verify(mTasksRepository).getTask(eq("1"), captor.capture()); // Simulate callback final Task testTask = new Task("TITLE", "DESCRIPTION"); captor.getValue().onTaskLoaded(testTask); // Then the task repository is queried and the view updated verify(view).showLoadingIndicator(false); verify(view, atLeastOnce()).setTitle(testTask.getTitle()); }
  23. Why you should use ThirtyInch • most likely better tested

    than your homemade one; Stateless or stateful • Grown and stabilized over the last 1.5 years in multiple projects • Gives you a stateful Presenter for Activities and Fragments which survives configuration changes Try it now!