Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Under Square's Hood

rjrjr
September 21, 2014

Under Square's Hood

How Square uses Dagger, Flow and Mortar to build its Register app. Because Fragments hate you and want you to suffer.

rjrjr

September 21, 2014
Tweet

More Decks by rjrjr

Other Decks in Programming

Transcript

  1. Square Style • Modularity viaViews • Injected by Dagger •

    Navigated by Flow • Presented and scoped by Mortar • Framework
  2. ! ! ! <com.example.player.PlayerView! 7777xmlns:android="http://schemas.android.com/apk/res/android"7 7777android:orientation="vertical"7 7777android:layout_width="match_parent"7android:layout_height="match_parent">7 777777 !!!!<ImageView! 77777777android:layout_width="match_parent"7android:layout_height="0dp"7

    77777777android:id="@+id/imageView"7 77777777android:layout_weight="1"/>7 !!!!<Button! 77777777android:layout_width="match_parent"7android:layout_height="wrap_content"7 77777777android:text="Play"7 77777777android:id="@+id/play_button"7 77777777android:layout_gravity="center_horizontal"7 77777777android:layout_weight="0"7 77777777android:layout_marginLeft="32dp"7android:layout_marginRight="32dp"7 77777777android:layout_marginTop="8dp"777android:layout_marginBottom="16dp"/>7 </com.example.player.PlayerView>7
  3. Dagger: @Inject Dependencies • Guicecc code generated, Android friendly, fast

    fast • @Inject “I want…” • @Module + @Provides “I make…”
  4. Field Injection • @Inject on fields which are dependencies •

    For the stuff created by Android — Activity,View • Self inflicted
  5. Providing Dependencies (“I make…”) • Constructor injected classes are good

    to go • Modules are classes that provide dependencies • @Module annotation on the module class • @Provides annotation on its factory methods • @Inject + @Module => ObjectGraph
  6. Square Style • Modularity via Views! • Injected by Dagger!

    • Navigated by Flow! • Presented and scoped by Mortar ✓ ✓
  7. Constantinople Sinister Exager… The Booker… Blue Rosebuds Laughing Song Bach

    is Dead Elvis & His Boss Laughing Song Play  AlbumListScreen7{7 } AlbumScreen7{7 77id7=723422;77 } TrackScreen7{77 77album7=723422;7 77track7=71;7 }
  8. ! public7class7AlbumListView7extends7LinearLayout7{7 77@Inject7Jukebox7jukebox;7 77@Inject7Flow7flow;7 ! 77@Override7protected7void7onFinishInflate()7{7 7777super.onFinishInflate();7 77 7777listView7=7findById(this,7R.id.album_list);7 7777adapter7=7new7AlbumListAdapter(jukebox);7

    7777listView.setAdapter(adapter);7 77 7777listView.setOnItemClickListener(new7OnItemClickListener()7{7 777777@Override7public7void7onItemClick(AdapterView<?>7parent,77 7777777777View7view,7int7position,7long7id)7{7 77777777flow.goTo(new7AlbumScreen(id));7 777777}7 7777});7 77}
  9. ! ! @Layout(R.layout.album_view)7 public7class7AlbumScreen7{7 77public7final7int7id;7 77 77public7AlbumScreen(int7id)7{7 7777this.id7=7id;7 77}7 77

    77@Override7 77public7boolean7equals(Object7o)7{7...7}7 77 77@Override7 77public7int7hashCode()7{7...7}! }7
  10. Flow: minimalist backstack • Stack of POJO “Screens,” like Intents

    or URLs • Store the flow Backstack in the Activity Bundle • Show screens when you’re told to
  11. ! public7class7MainActivity7extends7Activity77 7777implements7Flow.Listener7{7 ! 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);7 7777setContentView(R.layout.root_layout);7 ! !!!!Backstack7backstack;7 7777if7(b7==7null)7{7

    777777backstack7=7Backstack.single(new7AlbumList());7 7777}7else7{7 777777backstack7=77 7777777777Backstack.from(b.getParcelable(BACKSTACK),7parcer);7 7777}7 77 7777flow7=7new7Flow(backstack,7this);7 7777go(flow.getBackstack(),7REPLACE,7null);7 77}7
  12. ! public7class7MainActivity7extends7Activity77 7777implements7Flow.Listener7{7 ! 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);7 7777setContentView(R.layout.root_layout);7 ! !!!!Backstack7backstack;7 7777if7(b7==7null)7{7

    777777backstack7=7Backstack.single(new7AlbumList());7 7777}7else7{7 777777backstack7=77 7777777777Backstack.from(b.getParcelable(BACKSTACK),7parcer);7 7777}7 77 7777flow7=7new7Flow(backstack,7this);7 7777go(flow.getBackstack(),7REPLACE,7null);7 77}7
  13. Flow: minimalist backstack • Stack of POJO “Screens,” like Intents

    or URLs • Store the flow Backstack in the Activity Bundle • Show screens when you’re told to • Flow#goBack(), Flow#goUp()
  14. ! <com.example.flow.view.TabletLandscapeRoot7 7777xmlns:android="http://schemas.android.com/apk/res/android"7 77 7777android:id="@id/container"7 7777android:orientation="horizontal"7 7777android:layout_width="match_parent"7 7777android:layout_height="match_parent"7 7777>7 77<com.example.flow.view.NavList7

    777777android:id=“@+id/nav_list"7 777777android:layout_weight="20"7 777777android:layout_width="0dp"7 777777android:layout_height="match_parent"7 777777/>7 77<com.example.flow.screenswitcher.FrameScreenSwitcherView7 777777android:id="@+id/detail"7 777777android:layout_weight="80"7 777777android:layout_width="0dp"7 777777android:layout_height="match_parent"7 777777/>7 </com.example.flow.view.TabletLandscapeRoot>7
  15. ObjectGraph#plus() • Creates child graph, extends the parent with additional

    modules! • Allows creating “scopes” of dependencies
  16. 777 ! 77private7ObjectGraph7activityGraph;7 ! 77public7<T>7T7inject(T7thing)7{7 7777return7activityGraph.inject(thing);7 77}7 ! 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);7

    7777PlayerApp7app7=7(PlayerApp)7getApplication();7 7777activityGraph7=7app.getObjectGraph().plus(new7Module());7 7777 7777Backstack7backstack;7 7777if7(b7==7null)7{7 777777backstack7=7Backstack.single(new7AlbumList());7 7777}7else7{7 777777backstack7=77 7777777777Backstack.from(b.getParcelable(BACKSTACK),7parcer);7
  17. ! ! public7class7AlbumListView7extends7LinearLayout7{7 77@Inject7Jukebox7jukebox;7 77@Inject7Flow7flow;7 77 77public7AlbumListView(Context7context,7AttributeSet7attrs)7{7 7777super(context,7attrs);7 7777((MainActivity)context).inject(this);7 77}7

    77 77@Override7protected7void7onFinishInflate()7{7 7777super.onFinishInflate();7 77 7777listView7=7findById(this,7R.id.album_list);7 7777final7adapter7=7new7AlbumListAdapter();7 7777listView.setAdapter(adapter);7 77 7777listView.setOnItemClickListener(new7OnItemClickListener()7{7
  18. Square Style • Modularity via Views! • Injected by Dagger!

    • Navigated by Flow! • Presented and scoped by Mortar ✓ ✓ ✓
  19. P

  20. Mortar: managable, testable • MortarScope enshrines, simplifies ObjectGraph.plus() • Scopes

    nest to arbitrary depths • Each screen can be a Blueprint for its own shortlived scope
  21. @Singleton77 public7class7TrackViewPresenter7{7 77private7final7Jukebox7jukebox;7 77private7final7Flow7flow;7 77private7final7Tweeter7tweeter;7 77 77@Inject7public7TrackViewPresenter(Jukebox7jukebox,77 777777Flow7flow,7Tweeter7tweeter)7{7 7777this.jukebox7=7jukebox;7 7777this.flow7=7flow;7

    7777this.tweeter7=7tweeter;7 77}7 ! 77public7void7onPlayClicked()7{7 777if7(!jukebox.isPlaying())7{7 777777TrackScreen7screen7=7(TrackScreen)77 7777777777flow.getBackstack().current().getScreen();7 ! 777777jukebox.play(screen.albumId,7screen.trackId);7
  22. @Singleton7 public7class7TrackViewPresenter7{7 77private7final7Jukebox7jukebox;7 77private7final7TrackScreen7screen;7 77private7final7Tweeter7tweeter;7 77 77@Inject7public7TrackViewPresenter(Jukebox7jukebox,77 777777TrackScreen7screen,7Tweeter7tweeter)7{7 7777this.jukebox7=7jukebox;7 7777this.screen7=7screen;7

    7777this.tweeter7=7tweeter;7 77}7 ! 77public7void7onPlayClicked()7{7 777if7(!jukebox.isPlaying())7{7 777777jukebox.play(screen.albumId,7screen.trackId);7 777777tweeter.tweet("Playing7"7+7jukebox.playing().name());7 7777}7else7{7 777777jukebox.stop();7
  23. ! public7class7TrackViewPresenterTest7{! 77@Mock7Jukebox7jukebox;7 77@Mock7Tweeter7tweeter;7 77TrackScreen7screen7=7new7TrackScreen(100,75);77 ! 77TrackViewPresenter7presenter7=7 777777new7TrackViewPresenter(jukebox,7screen,7tweeter);7 77 77@Before7public7void7setup()7{7

    7777initMocks(this);7 77}7 77 77@Test7public7void7playClick_stopsWhenPlaying()7{7 7777when(jukebox.isPlaying()).thenReturn(true);7 7777presenter.onPlayClicked();7 7777verify(jukebox).stop();7 77}7 }
  24. Mortar: managable, testable • MortarScope enshrines, simplifies ObjectGraph.plus() • Scopes

    nest to arbitrary depths • Each screen can be a Blueprint for its own shortlived scope • Presenter class with onLoad(Bundle) and onSave(Bundle) lifecycle
  25. ! ! @Singleton77 public7class7TrackViewPresenter7{7 77private7final7Jukebox7jukebox;7 77private7final7TrackScreen7screen;7 77private7final7Tweeter7tweeter;7 77 77@Inject7public7TrackViewPresenter(Jukebox7jukebox,77 777777Trackscreen7screen,7Tweeter7tweeter)7{7

    7777this.jukebox7=7jukebox;7 7777this.screen7=7screen;7 7777this.tweeter7=7tweeter;7 77}7 ! 77public7void7onPlayClicked()7{7 777if7(!jukebox.isPlaying())7{7 777777jukebox.play(screen.albumId,7screen.trackId);7 777777tweeter.tweet("Playing7"7+7jukebox.playing().name());7
  26. ! ! @Singleton77 public7class7TrackViewPresenter7extends7ViewPresenter<TrackView>7{7 77private7final7Jukebox7jukebox;7 77private7final7TrackScreen7screen;7 77private7final7Tweeter7tweeter;7 77 77@Inject7public7TrackViewPresenter(Jukebox7jukebox,77 777777Trackscreen7screen,7Tweeter7tweeter)7{7

    7777this.jukebox7=7jukebox;7 7777this.screen7=7screen;7 7777this.tweeter7=7tweeter;7 77}7 ! 77public7void7onPlayClicked()7{7 777if7(!jukebox.isPlaying())7{7 777777jukebox.play(screen.albumId,7screen.trackId);7 777777tweeter.tweet("Playing7"7+7jukebox.playing().name());7
  27. ! ! /**#Called#the#view#takes#this#presenter.#*/7 void7onEnterScope()7{! }7 ! /**#Called#each#time#takeView()#receives#a#new#view.#*/7 void7onLoad(Bundle7savedInstanceState)!{! !!getView().setStuffUpIGuess();! }7

    ! /**#Called#from#Activity#onSaveInstanceState.#*/7 void7onSave(Bundle7outState)!{! }7 ! /**#Called#when#the#scope#is#destroyed.#*/7 void7onExitScope()7{! }7
  28. Mortar: managable, testable • MortarScope enshrines, simplifies ObjectGraph.plus() • Scopes

    nest to arbitrary depths • Each screen can be a Blueprint for its own shortlived scope • Presenter class with onLoad(Bundle) and onSave(Bundle) lifecycle • Rooted in the Application and Activity
  29. ! public7class7PlayerApp7extends7Application7{7 77private7MortarScope7rootScope;7 77 77@Override7public7void7onCreate()7{7 7777super.onCreate();7 77 7777rootScope7=7Mortar.createRootScope(new7AppModule(this));7 77}7 77

    77@Override7public7Object7getSystemService(String7name)7{7 7777if7(Mortar.isScopeSystemService(name))7{7 777777return7rootScope;7 7777}7 77 7777return7super.getSystemService(name);7 77}7 }7
  30. 777 public7class7MainActivity7extends7Activity7implements7Flow.Listener7{7 77private7ObjectGraph7activityGraph;7 ! 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);! !!!!setContentView(R.layout.root_layout);! 77 7777MortarScope7parentScope7=7Mortar.getScope(getApplication());7 7777activityScope7=7Mortar7

    77777777.requireActivityScope(parentScope,7new7Blueprint());7 77 7777Backstack7backstack;7 7777if7(b7==7null)7{7 777777backstack7=7Backstack.single(new7AlbumList());7 7777}7else7{7 777777backstack7=77 7777777777Backstack.from(b.getParcelable(BACKSTACK),7parcer);7 7777}7
  31. 777 ! ! 77public7<T>7T7inject(T!thing)7{7 7777return7activityGraph.inject(thing);7 77}! ! ! ! 77

    77public7AlbumListView(Context7context,7AttributeSet7attrs)7{7 7777super(context,7attrs);7 7777((MainActivity)context).inject(this);7 77}7
  32. ! ! 77@Override7public7Object7getSystemService(String7name)7{7 7777if7(Mortar.isScopeSystemService(name))7{7 777777return7activityScope;7 7777}! !777return7super.getSystemService(name);7 77}7 ! !

    77public7AlbumListView(Context7context,7AttributeSet7attrs)7{7 7777super(context,7attrs);7 7777Mortar.inject(context,!this);7 77}7
  33. Square Style • Modularity via Views! • Injected by Dagger!

    • Navigated by Flow! • Presented and scoped by Mortar ✓ ✓ ✓ ✓