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

Fragments - The Solution To (And Cause Of) All Of Android's Problems

Fragments - The Solution To (And Cause Of) All Of Android's Problems

My 2017 CodeMash talk on Fragments and Architecture on Android.

Michael Yotive

January 15, 2017
Tweet

More Decks by Michael Yotive

Other Decks in Technology

Transcript

  1. FRAGMENTS: THE SOLUTION TO (AND CAUSE OF) ALL OF ANDROID’S

    PROBLEMS AGENDA ▸ Google I/O 2016 “What the Fragment” Recap. ▸ Fragment Best Practices ▸ Community reaction ▸ Square ▸ Fragmentless architecture ▸ Libraries!
  2. WHAT THE FRAGMENT ACTIVITY ▸ Traditional Entry Point ▸ Goals

    ▸ Apps stay in the state where you left them. ▸ Apps can call other apps to complete a task via an Intent.
  3. ▸ Denali (Alaska Range) - 6,190m (20,210 ft) ▸ Mount

    Everest (Himalaya) - 8,848m (29,029 ft) ▸ MainActivity.java - 9,000 lines (100 WTF/s)
  4. WHAT THE FRAGMENT FRAGMENTS ▸ Goals ▸ Break up giant

    Activity classes ▸ Encapsulate Navigation State ▸ Decompose Master/Detail Flows
  5. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE public class SpeakerListFragment extends

    Fragment { private CodeMashAPI codeMashAPI; private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; 
 private View.OnClickListener speakerClickListener = { … }; public SpeakerListFragment() { // Required empty public constructor } public static SpeakerListFragment newInstance() { return new SpeakerListFragment(); }
 
 …
 }
  6. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE public class SpeakerListFragment extends

    Fragment { private CodeMashAPI codeMashAPI; private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; 
 private View.OnClickListener speakerClickListener = { … }; … @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_speaker, container, false); speakerAdapter = new SpeakerAdapter(getContext(), Collections.<Speaker>emptyList(),
 speakerClickListener); speakerRecyclerView = (RecyclerView)view.findViewById(R.id.rv_speakers); speakerRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); speakerRecyclerView.setAdapter(speakerAdapter); return view; } @Override public void onResume() { … } }
  7. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE public class SpeakerListFragment extends

    Fragment { private CodeMashAPI codeMashAPI; private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; 
 private View.OnClickListener speakerClickListener = { … }; … @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_speaker, container, false); speakerAdapter = new SpeakerAdapter(getContext(), Collections.<Speaker>emptyList(),
 speakerClickListener); speakerRecyclerView = (RecyclerView)view.findViewById(R.id.rv_speakers); speakerRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); speakerRecyclerView.setAdapter(speakerAdapter); return view; } @Override public void onResume() { … } }
  8. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE public class SpeakerListFragment extends

    Fragment { private CodeMashAPI codeMashAPI; private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; 
 private View.OnClickListener speakerClickListener = { … }; … @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_speaker, container, false); speakerAdapter = new SpeakerAdapter(getContext(), Collections.<Speaker>emptyList(),
 speakerClickListener); speakerRecyclerView = (RecyclerView)view.findViewById(R.id.rv_speakers); speakerRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); speakerRecyclerView.setAdapter(speakerAdapter); return view; } @Override public void onResume() { … } }
  9. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE private View.OnClickListener speakerClickListener =

    new View.OnClickListener() { @Override public void onClick(View view) { int itemPosition = speakerRecyclerView.getChildLayoutPosition(view); Speaker speaker = speakerAdapter.getItem(itemPosition); FragmentUtility.goToFragment(getFragmentManager(), SpeakerDetailFragment.newInstance(speaker), R.id.main_content, true, TransitionType.SlideHorizontal); } };
  10. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE FragmentTransaction ft = fragmentManager.beginTransaction();

    String tag = fragment.getClass().getSimpleName(); if(addToBackstack) { ft.addToBackStack(tag); } ft.setCustomAnimations(enter, exit, popEnter, popExit) .replace(containerId, fragment, tag) .commit(); // FragmentUtility.java
  11. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE public class SpeakerListFragment extends

    Fragment { private CodeMashAPI codeMashAPI; private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; … @Override public void onResume() { super.onResume(); codeMashAPI.GetSpeakers().enqueue(new Callback<List<Speaker>>() { @Override public void onResponse(Call<List<Speaker>> call, Response<List<Speaker>> response)
 { if(response.isSuccessful()){ speakerAdapter.swap(response.body()); } } @Override public void onFailure(Call<List<Speaker>> call, Throwable t) 
 { Log.e(TAG, "onFailure: ", t); } }); } }
  12. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE public class MainActivity extends

    AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, SpeakerFragment.newInstance()) .commitNow(); } } }
  13. DEMO FRAGMENT CODE MASH FRAGMENT EXAMPLE - SUMMARY ▸ Business

    logic is contained within each Fragment - not the Activity. ▸ Dynamic fragment created and added to our activity through FragmentManager. ▸ Through the FragmentManager, we have the ability to traverse fragments and apply animations to them.
  14. WHAT THE FRAGMENT INITIAL COMPLAINTS WITH FRAGMENTS ▸ Too general,

    which makes them difficult to explain. ▸ Google didn’t provide much guidance initially. ▸ Tricky API ▸ <fragment> tag ▸ Child Fragments ▸ FragmentManager vs ChildFragmentManager ▸ Additional Lifecycle…
  15. WHAT THE FRAGMENT BUGS WITH CHILD FRAGMENTS ▸ executePendingTransactions ▸

    Problem: Child Fragments would not be brought up to new state until after parent onCreate. (Fixed in Nougat) ▸ Problem: All transactions are executed right now. ▸ (Nougat) FragmentTransaction.commitNow()
  16. WHAT THE FRAGMENT BEST PRACTICES WITH FRAGMENTS ▸ Fragments ==

    Activity ▸ Composable entry points that return to state. ▸ May or may not have UI ▸ Fragments != Fancy View Groups
  17. WHAT THE FRAGMENT FRAGMENT OR CUSTOM VIEW GROUP? ▸ Does

    the UI implement policy? ▸ CoordinatorLayout does a lot of fancy stuff, but calls out to higher component to enforce policy. ▸ Do you have to use the back button to navigate? ▸ Do you have to worry about state? ▸ Traditionally an issue with child fragments, but most of the inconsistencies have been fixed in Nougat.
  18. WHAT THE FRAGMENT BEST PRACTICES WITH FRAGMENTS ▸ Fragments can

    depend on views, but not the other way around. ▸ They orchestrate views they sit above or coordinate with other components. ▸ Aware of external lifecycle.
  19. WHAT THE FRAGMENT DEALING WITH LIFECYCLE ▸ Just because there

    are a lot of states, doesn’t mean you have to care about all of them. ▸ Yes, there are a lot of events and they are overwhelming. ▸ Think: ▸ “What minimum state will this component be in?”
  20. WHAT THE FRAGMENT BEST PRACTICES WITH FRAGMENTS ▸ Fragments should

    not take on more responsibility than needed. ▸ Keep scope limited to the current fragment. ▸ Fragments work best in isolation.
  21. SQUARE + FRAGMENTS ADVOCATING AGAINST FRAGMENTS ▸ Tightly coupled view

    logic and business logic, which makes unit testing difficult. ▸ Fragment Creation Magic ‣FooFragment.newInstance(bar); ‣DialogFragment dialogFragment = new DialogFragment() { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ... } }; dialogFragment.show(fragmentManager, tag);
  22. SQUARE + FRAGMENTS LESSONS LEARNED ▸ Single Activity Interface ▸

    No need for new APIs. You have the tools you need: ▸ Activities, Views, and Layout Inflaters ▸ You can write your own backstack manager.
  23. SQUARE + FRAGMENTS FLOW ▸ Flow == Navigation Controller +

    Backstack ▸ Describes UI as “Screens”, a POJO that specifies which View to use and any parameters it might need.
  24. SQUARE + FRAGMENTS MORTAR ▸ Mortar: A blueprint for your

    app. ▸ Creates and manages scoped objects ▸ Simplified lifecycle, if you need it. ▸ Ability to save and restore state.
  25. SQUARE + FRAGMENTS MORTAR / FLOW - PROS AND CONS

    ▸ Pros ▸ No fragments (yay!) ▸ Testing becomes easier. ▸ Mortar Scoping means application will be more efficient.
  26. SQUARE + FRAGMENTS MORTAR / FLOW - PROS AND CONS

    ▸ Cons ▸ Steep learning curve. ▸ Mountain of boilerplate. ▸ Still in Alpha ▸ Flow initial release Nov. 12, 2013 ▸ 1.0.0-Alpha2 released Sept 2nd, 2016 ▸ Mortar initial release - Jan 21, 2014 ▸ 0.20 release was Feb. 1st, 2016
  27. SQUARE + FRAGMENTS LESSONS LEARNED ▸ Square set the tone

    for being able to achieve fragment-less architecture. ▸ Square used existing patterns to solve their problems.
  28. PATTERN GAME MODEL VIEW CONTROLLER ▸ Pros ▸ Separation of

    Concerns. ▸ Promotes re-useable components.
  29. PATTERN GAME MODEL VIEW CONTROLLER ▸ Cons ▸ Lots of

    responsibility on the controller. ▸ Difficult to unit test.
  30. PATTERN GAME NAME THAT PATTERN View View View View Interface

    View Interface View Interface Presenter Presenter Presenter Model Model
  31. PATTERN GAME MODEL VIEW PRESENTER ▸ Pros ▸ Still have

    separation of concerns. ▸ Testable.
  32. PATTERN GAME MODEL - VIEW - PRESENTER ▸ Cons ▸

    A lot more classes. ▸ Higher complexity/steeper curve.
  33. MODEL VIEW PRESENTER SOURCES ▸ https://github.com/googlesamples/android-architecture ▸ Today we’ll talk

    about fragment-less MVP:
 
 https://github.com/Syhids/android-architecture/tree/todo-mvp- fragmentless
  34. MODEL VIEW PRESENTER VIEWS ▸ Job is to inform the

    presenter of events. ▸ Allow the presenter to tell it what to do, but hide implementation from presenter. ▸ Doesn’t have to be the android.view.View class ▸ Could be a fragment that communicates with a presenter via a View Interface.
  35. MODEL VIEW PRESENTER VIEWS public class SpeakerListView extends FrameLayout implements

    SpeakerContract.View { private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; private SpeakerListContract.Presenter presenter; public SpeakerListView(Context context) { super(context); init(); } public SpeakerListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SpeakerListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { … }
 
 … }
  36. MODEL VIEW PRESENTER VIEWS public class SpeakerListView extends FrameLayout implements

    SpeakerContract.View { private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; private SpeakerListContract.Presenter presenter; public SpeakerListView(Context context) { super(context); init(); } public SpeakerListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SpeakerListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { … }
 
 … }
  37. MODEL VIEW PRESENTER VIEWS public class SpeakerListView extends FrameLayout implements

    SpeakerContract.View { private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; private SpeakerListContract.Presenter presenter; public SpeakerListView(Context context) { super(context); init(); } public SpeakerListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SpeakerListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { … }
 
 … }
  38. MODEL VIEW PRESENTER VIEWS INTERFACE ▸ Contract between View and

    Presenter. ▸ Let the presenter tell the view what to do. ▸ Let the presenter know of any event that happens inside view. ▸ Button click, scroll event, ect… ▸ Having this contract allows for deeper unit testing.
  39. MODEL VIEW PRESENTER VIEW INTERFACE interface View extends BaseView<Presenter>{ void

    updateSpeakerList(List<Speaker> speakers); Speaker onSpeakerClick(int pos); } public interface BaseView<T extends BasePresenter> { void setPresenter(T presenter); }
  40. MODEL VIEW PRESENTER VIEW INTERFACE interface View extends BaseView<Presenter>{ void

    updateSpeakerList(List<Speaker> speakers); Speaker onSpeakerClick(int pos); } public interface BaseView<T extends BasePresenter> { void setPresenter(T presenter); }
  41. MODEL VIEW PRESENTER VIEW INTERFACE public class SpeakerListView extends FrameLayout

    implements SpeakerContract.View { private RecyclerView speakerRecyclerView; private SpeakerAdapter speakerAdapter; private SpeakerListContract.Presenter presenter; … private void init() { … } @Override public void updateSpeakerList(List<Speaker> speakers) { speakerAdapter.swap(speakers); } @Override public Speaker onSpeakerClick(int pos) { return speakerAdapter.getItem(pos); } @Override public void setPresenter(SpeakerListContract.Presenter presenter) { this.presenter = presenter; } }
  42. MODEL VIEW PRESENTER PRESENTER ▸ Mediator between the view and

    the model. ▸ Handles events. ▸ What happens with a button click on the view?
  43. MODEL VIEW PRESENTER PRESENTER public class SpeakerListPresenter implements SpeakerListContract.Presenter {

    private CodeMashAPI codeMashAPI; private SpeakerListContract.View view; private Call<List<Speaker>> speakerCall; public SpeakerListPresenter(CodeMashAPI codeMashAPI, SpeakerListContract.View view){ this.codeMashAPI = codeMashAPI; this.view = view; this.view.setPresenter(this); } … }
  44. MODEL VIEW PRESENTER PRESENTER public class SpeakerListPresenter implements SpeakerListContract.Presenter {

    private CodeMashAPI codeMashAPI; private SpeakerContract.View view; private Call<List<Speaker>> speakerCall; public SpeakerListPresenter(CodeMashAPI codeMashAPI, SpeakerListContract.View view){ this.codeMashAPI = codeMashAPI; this.view = view; this.view.setPresenter(this); } … }
  45. MODEL VIEW PRESENTER PRESENTER public class SpeakerListPresenter implements SpeakerListContract.Presenter {

    private CodeMashAPI codeMashAPI; private SpeakerContract.View view; private Call<List<Speaker>> speakerCall; public SpeakerListPresenter(CodeMashAPI codeMashAPI, SpeakerListContract.View view){ this.codeMashAPI = codeMashAPI; this.view = view; this.view.setPresenter(this); } … }
  46. MODEL VIEW PRESENTER PRESENTER interface Presenter extends BasePresenter{ void getSpeakerList();

    } public interface BasePresenter { void start(); void stop(); }
  47. MODEL VIEW PRESENTER PRESENTER interface Presenter extends BasePresenter{ void getSpeakerList();

    } public interface BasePresenter { void start(); void stop(); }
  48. MODEL VIEW PRESENTER PRESENTER public class SpeakerListPresenter implements SpeakerContract.Presenter {

    … public SpeakerListPresenter(CodeMashAPI codeMashAPI, SpeakerContract.View view){ … } @Override public void start() { getSpeakerList(); } @Override public void stop() { … } @Override public void getSpeakerList() { speakerCall = codeMashAPI.GetSpeakers(); speakerCall.enqueue(new Callback<List<Speaker>>() { @Override public void onResponse(Call<List<Speaker>> call, Response<List<Speaker>> response) { if(response.isSuccessful()){ view.updateSpeakerList(response.body()); } } @Override public void onFailure(Call<List<Speaker>> call, Throwable t) { Log.e(TAG, "Error calling CodeMash API", t); } }); } }
  49. MODEL VIEW PRESENTER PRESENTER public class SpeakerListPresenter implements SpeakerContract.Presenter {

    … public SpeakerListPresenter(CodeMashAPI codeMashAPI, SpeakerContract.View view){ … } @Override public void start() { getSpeakerList(); } @Override public void stop() { … } @Override public void getSpeakerList() { speakerCall = codeMashAPI.GetSpeakers(); speakerCall.enqueue(new Callback<List<Speaker>>() { @Override public void onResponse(Call<List<Speaker>> call, Response<List<Speaker>> response) { if(response.isSuccessful()){ view.updateSpeakerList(response.body()); } } @Override public void onFailure(Call<List<Speaker>> call, Throwable t) { Log.e(TAG, "Error calling CodeMash API", t); } }); } }
  50. MODEL VIEW PRESENTER PRESENTER public class SpeakerListPresenter implements SpeakerContract.Presenter {

    … public SpeakerListPresenter(CodeMashAPI codeMashAPI, SpeakerContract.View view){ … } @Override public void start() { getSpeakerList(); } @Override public void stop() { 
 if( speakerCall != null ){ speakerCall.cancel(); } } @Override public void getSpeakerList() { … } }
  51. MODEL VIEW PRESENTER PRESENTER public class MainActivity extends AppCompatActivity {

    private SpeakerListView speakerListView; private SpeakerListPresenter speakerListPresenter; private CodeMashAPI codeMashAPI; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); speakerListView = (SpeakerListView)findViewById(R.id.speakerListView); codeMashAPI = BaseApplication.getApplication(this).getApplicationComponent().CodeMashAPI(); speakerListPresenter = new SpeakerListPresenter(codeMashAPI, speakerListView); } @Override protected void onResume() { super.onResume(); speakerListPresenter.start(); } @Override protected void onStop() { super.onStop(); speakerListPresenter.stop(); } }
  52. MODEL VIEW PRESENTER PRESENTER <?xml version="1.0" encoding="utf-8"?> <FrameLayout 
 xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.myotive.mvpexample.MainActivity"> <com.example.myotive.mvpexample.codemash.SpeakerListView android:id="@+id/speakerListView" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
  53. MODEL VIEW PRESENTER PRESENTER public class MainActivity extends AppCompatActivity {

    private SpeakerListView speakerListView; private SpeakerListPresenter speakerListPresenter; private CodeMashAPI codeMashAPI; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); speakerListView = (SpeakerListView)findViewById(R.id.speakerListView); codeMashAPI = BaseApplication.getApplication(this).getApplicationComponent().CodeMashAPI(); speakerListPresenter = new SpeakerListPresenter(codeMashAPI, speakerListView); } @Override protected void onResume() { super.onResume(); speakerListPresenter.start(); } @Override protected void onStop() { super.onStop(); speakerListPresenter.stop(); } }
  54. MODEL VIEW PRESENTER PRESENTER public class MainActivity extends AppCompatActivity {

    private SpeakerListView speakerListView; private SpeakerListPresenter speakerListPresenter; private CodeMashAPI codeMashAPI; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); speakerListView = (SpeakerListView)findViewById(R.id.speakerListView); codeMashAPI = BaseApplication.getApplication(this).getApplicationComponent().CodeMashAPI(); speakerListPresenter = new SpeakerListPresenter(codeMashAPI, speakerListView); } @Override protected void onResume() { super.onResume(); speakerListPresenter.start(); } @Override protected void onStop() { super.onStop(); speakerListPresenter.stop(); } }
  55. MODEL VIEW PRESENTER PRESENTER public class MainActivity extends AppCompatActivity {

    private SpeakerListView speakerListView; private SpeakerListPresenter speakerListPresenter; private CodeMashAPI codeMashAPI; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); speakerListView = (SpeakerListView)findViewById(R.id.speakerListView); codeMashAPI = BaseApplication.getApplication(this).getApplicationComponent().CodeMashAPI(); speakerListPresenter = new SpeakerListPresenter(codeMashAPI, speakerListView); } @Override protected void onResume() { super.onResume(); speakerListPresenter.start(); } @Override protected void onStop() { super.onStop(); speakerListPresenter.stop(); } }
  56. MODEL VIEW PRESENTER PRESENTER public class MainActivity extends AppCompatActivity {

    private SpeakerListView speakerListView; private SpeakerListPresenter speakerListPresenter; private CodeMashAPI codeMashAPI; @Override protected void onCreate(Bundle savedInstanceState) { … } @Override protected void onResume() { super.onResume(); speakerListPresenter.start(); } @Override protected void onStop() { super.onStop(); speakerListPresenter.stop(); } }
  57. MODEL VIEW PRESENTER UNIT TESTING @Mock private SpeakerListContract.View view; @Mock

    private CodeMashAPI codeMashAPI; @Mock private Call<List<Speaker>> mockCall; @Captor private ArgumentCaptor<Callback<List<Speaker>>> captor; private SpeakerListPresenter presenter; @Before public void setup(){ MockitoAnnotations.initMocks(this); presenter = new SpeakerListPresenter(codeMashAPI, view); } @Test public void test_GetPresenterStarted(){ // arrange … // act … // assert … }
  58. MODEL VIEW PRESENTER UNIT TESTING @Mock private SpeakerListContract.View view; @Mock

    private CodeMashAPI codeMashAPI; @Mock private Call<List<Speaker>> mockCall; @Captor private ArgumentCaptor<Callback<List<Speaker>>> captor; private SpeakerListPresenter presenter; @Before public void setup(){ MockitoAnnotations.initMocks(this); presenter = new SpeakerListPresenter(codeMashAPI, view); } @Test public void test_GetPresenterStarted(){ // arrange … // act … // assert … }
  59. MODEL VIEW PRESENTER UNIT TESTING @Mock private SpeakerListContract.View view; @Mock

    private CodeMashAPI codeMashAPI; @Mock private Call<List<Speaker>> mockCall; @Captor private ArgumentCaptor<Callback<List<Speaker>>> captor; private SpeakerListPresenter presenter; @Before public void setup(){ … } @Test public void test_GetPresenterStarted(){ // arrange List<Speaker> speakers = new ArrayList<>(); speakers.add(new Speaker()); when(codeMashAPI.GetSpeakers()).thenReturn(mockCall); Response<List<Speaker>> response = Response.success(speakers); // act presenter.start(); // assert verify(mockCall).enqueue(captor.capture()); captor.getValue().onResponse(null, response); verify(view).showLoading(); verify(view).hideLoading(); verify(view).updateSpeakerList(speakers); }
  60. MODEL VIEW PRESENTER UNIT TESTING @Mock private SpeakerListContract.View view; @Mock

    private CodeMashAPI codeMashAPI; @Mock private Call<List<Speaker>> mockCall; @Captor private ArgumentCaptor<Callback<List<Speaker>>> captor; private SpeakerListPresenter presenter; @Before public void setup(){ … } @Test public void test_GetPresenterStarted(){ // arrange List<Speaker> speakers = new ArrayList<>(); speakers.add(new Speaker()); when(codeMashAPI.GetSpeakers()).thenReturn(mockCall); Response<List<Speaker>> response = Response.success(speakers); // act presenter.start(); // assert verify(mockCall).enqueue(captor.capture()); captor.getValue().onResponse(null, response); verify(view).showLoading(); verify(view).hideLoading(); verify(view).updateSpeakerList(speakers); }
  61. MODEL VIEW PRESENTER UNIT TESTING @Mock private SpeakerListContract.View view; @Mock

    private CodeMashAPI codeMashAPI; @Mock private Call<List<Speaker>> mockCall; @Captor private ArgumentCaptor<Callback<List<Speaker>>> captor; private SpeakerListPresenter presenter; @Before public void setup(){ … } @Test public void test_GetPresenterStarted(){ // arrange List<Speaker> speakers = new ArrayList<>(); speakers.add(new Speaker()); when(codeMashAPI.GetSpeakers()).thenReturn(mockCall); Response<List<Speaker>> response = Response.success(speakers); // act presenter.start(); // assert verify(mockCall).enqueue(captor.capture()); captor.getValue().onResponse(null, response); verify(view).showLoading(); verify(view).hideLoading(); verify(view).updateSpeakerList(speakers); }
  62. MODEL VIEW PRESENTER TODO ▸ Navigation (backstack) ▸ Multiple Activities

    ▸ If using Fragment as presenter container (instead of Activity), you can still use FragmentManager. ▸ Roll your own. Can implemented as a simple Stack or List. ▸ Square’s Flow library ▸ Screen orientation changes and managing View State
  63. MODEL VIEW PRESENTER LIBRARIES! ▸ Mosby ▸ MVP Library with

    ViewState preservation ▸ Used in conjunction with Flow for navigation ▸ ThirtyInch ▸ Presenters can survive the whole lifetime of an activity - only gets destroyed when Activity finishes ▸ Nucleus ▸ RxJava support
  64. CONDUCTOR CONDUCTOR ▸ Framework for building View-Based android applications. ▸

    Architecture agnostic. ▸ Easy integration with MVP / MVVM / VIPER / MVC ▸ State Persistence ▸ Navigation and Backstack handling. ▸ Supports transitions between views ▸ Optionally Supports RxJava Lifecycle
  65. CONDUCTOR CONDUCTOR public class SpeakerListController extends Controller { private CodeMashAPI

    codeMashAPI; … public SpeakerListController(CodeMashAPI api){ this.codeMashAPI = api; } @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_speaker, container, false);
 
 …
 return view; } @Override protected void onAttach(@NonNull View view) { … } @Override protected void onDetach(@NonNull View view) { … } }
  66. CONDUCTOR CONDUCTOR public class SpeakerListController extends Controller { private RecyclerView

    speakerRecyclerView; private SpeakerAdapter speakerAdapter; private View.OnClickListener speakerClickListener = new View.OnClickListener() { … }; public SpeakerListController(CodeMashAPI api){ … } @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_speaker, container, false); this.container = container; if(speakerAdapter == null) { speakerAdapter = new SpeakerAdapter(getActivity(), Collections.<Speaker>emptyList(), speakerClickListener); } speakerRecyclerView = (RecyclerView)view.findViewById(R.id.rv_speakers); speakerRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); speakerRecyclerView.setAdapter(speakerAdapter); return view; } @Override protected void onAttach(@NonNull View view) { … } @Override protected void onDetach(@NonNull View view) { … } }
  67. CONDUCTOR CONDUCTOR public class SpeakerListController extends Controller { … private

    View.OnClickListener speakerClickListener = new View.OnClickListener() { @Override public void onClick(View view) { int itemPosition = speakerRecyclerView.getChildLayoutPosition(view); Speaker speaker = speakerAdapter.getItem(itemPosition); SpeakerDetailController detailController = new SpeakerDetailController(speaker); RouterTransaction transaction = RouterTransaction.with(detailController) .pushChangeHandler(new HorizontalChangeHandler()) .popChangeHandler(new HorizontalChangeHandler());
 getRouter().pushController(transaction); } }; 
 … @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { … if(speakerAdapter == null) { speakerAdapter = new SpeakerAdapter(getActivity(), Collections.<Speaker>emptyList(), speakerClickListener); }
 … } … }
  68. CONDUCTOR CONDUCTOR public class SpeakerDetailController extends Controller { private Speaker

    speaker; private ImageView speakerImage; private TextView speakerBio; public SpeakerDetailController(Speaker speaker){ this.speaker = speaker; } @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_speaker_detail, container, false); … return view; } … @Override public boolean handleBack() { return super.handleBack(); } }
  69. CONDUCTOR CONDUCTOR public class SpeakerDetailController extends Controller { private Speaker

    speaker; private ImageView speakerImage; private TextView speakerBio; public SpeakerDetailController(Speaker speaker){ … } @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_speaker_detail, container, false); … return view; } … @Override public boolean handleBack() { return super.handleBack(); } }
  70. CONDUCTOR CONDUCTOR public class MainActivity extends AppCompatActivity implements ProgressBarProvider, ActionBarProvider

    { private FrameLayout mainContent; private ProgressBar progressBar; private Router router; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CodeMashAPI codeMashAPI = BaseApplication.getApplication(this) .getApplicationComponent() .CodeMashAPI(); mainContent = (FrameLayout) findViewById(R.id.main_content); progressBar = (ProgressBar) findViewById(R.id.loading); router = Conductor.attachRouter(this, mainContent, savedInstanceState); if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(new SpeakerListController(codeMashAPI))); } } }
  71. CONDUCTOR SHOULD I USE CONDUCTOR? ▸ Pros ▸ Simplified lifecycle

    ▸ No async transactions (no IllegalStateExceptions) ▸ Integrates well with other libraries (Mosby) ▸ Jake Wharton likes it
  72. CONDUCTOR SHOULD I USE CONDUCTOR? ▸ Cons ▸ Not a

    ton of examples. If you run into issues, you may be on your own.
  73. CONCLUSION ANDROID COMPONENT MODEL ▸ The Android components (Activities/Fragments, Services,

    etc.) are simply entry points into your application. ▸ Android Framework tries (to a certain extent) to stay out of your way when it comes to architecture patterns. ▸ Dianne Hackborn’s (Android Framework Team) post on Android Architecture ▸ https://goo.gl/MZYvSF