Save 37% off PRO during our Black Friday Sale! »

Under Square's Hood

82ee6bce819efe5b9cc6c51dea03e8da?s=47 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.

82ee6bce819efe5b9cc6c51dea03e8da?s=128

rjrjr

September 21, 2014
Tweet

Transcript

  1. Under Square’s Hood Ray Ryan

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

    Navigated by Flow • Presented and scoped by Mortar • Framework
  3. Your Inalienable Right! to Modularity android.app.Activity android.app.Fragment android.view.View

  4. Play 

  5. ! ! public7class7MainActivity7extends7Activity7{7 777 77@Override7protected7void7onCreate(Bundle7savedInstanceState)7{7 7777super.onCreate(savedInstanceState);7 !!!!setContentView(R.layout.root_layout);! 77}7 }7

  6. ! ! ! <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
  7. ! ! public7class7PlayerView7extends7LinearLayout7{7 77private7final7Jukebox7jukebox;7 77private7final7Tweeter7tweeter;7 77 77public7PlayerView(Context7context,7AttributeSet7attrs)7{7 7777super(context,7attrs);7 7777PlayerApp7app7=7(PlayerApp)7context.getApplicationContext();7 7777jukebox7=7app.getJukebox();7

    7777tweeter7=7app.getTweeter();7 77}7
  8. ! ! 7@Override7protected7void7onFinishInflate()7{7 7777super.onFinishInflate();7 7777View7playButton7=7findViewById(R.id.play_button);! ! 7777playButton.setOnClickListener(new7OnClickListener()7{7 777777@Override7public7void7onClick(View7view)7{7 77777777if7(!jukebox.isPlaying())7{7 7777777777jukebox.play();7

    7777777777tweeter.tweet("Playing7"7+7jukebox.playing().name());7 77777777}7else7{7 7777777777jukebox.stop();7 77777777}7 777777}7 7777});7 77}
  9. ! ! public7class7PlayerApp7extends7Application7{7 77public7Jukebox7getJukebox()7{7 7777return7jukebox;7 77}7 77 77public7Tweeter7getTweeter()7{7 7777return7tweeter;7 77}!

    }7
  10. Play  PlayerApp Jukebox Tweeter

  11. PlayerApp Tweeter TwitterApi Play  Jukebox AudioStreamer

  12. PlayerApp Play  Tweeter Jukebox AudioStreamer TwitterApi OkHttpClient SharedPreferences

  13. ! ! public7class7PlayerApp7extends7Application7{7 77 77private!AudioStreamer7audioStreamer;7 77private!OkHttpClient7httpClient;7 77private!Jukebox7jukebox;7 !!private!TwitterApi!twitterApi;! 77private!SharedPreferences7prefs;7 77private!Tweeter7tweeter;7

    77
  14. ! ! !7@Override7public7void7onCreate()7{7 7777super.onCreate();7 77 7777httpClient7=7new7OkHttpClient();7 7777audioStreamer7=7new7AudioStreamer(httpClient);7 7777twitterApi7=7new7TwitterApi(httpClient);7 77 7777prefs7=7getSharedPreferences("prefs",7MODE_PRIVATE);7

    7777String7twitterUser7=7prefs.getString("twittercuser",7null);7 7777tweeter7=7new7Tweeter(twitterUser,7twitterApi);7 77 7777jukebox7=7new7Jukebox(audioStreamer);7 77}
  15. That could turn into a lot of code

  16. Dagger: @Inject Dependencies speakerdeck.com/jakewharton/android-apps-with-dagger-devoxx-2013

  17. Dagger: @Inject Dependencies • Guicecc code generated, Android friendly, fast

    fast • @Inject “I want…” • @Module + @Provides “I make…”
  18. Requesting Dependencies (“I want…”) • @Inject annotation required • Field

    injection • Constructor injection
  19. Field Injection • @Inject on fields which are dependencies •

    For the stuff created by Android — Activity,View • Self inflicted
  20. ! ! public7class7PlayerView7extends7LinearLayout7{7 77private7final7Jukebox7jukebox;7 77private7final7Tweeter7tweeter;7 77 77public7PlayerView(Context7context,7AttributeSet7attrs)7{7 7777super(context,7attrs);7 7777PlayerApp7app7=77 77777777(PlayerApp)7context.getApplicationContext();7

    7777jukebox7=7app.getJukebox();7 7777tweeter7=7app.getTweeter();7 77}7 }7
  21. ! ! public7class7PlayerView7extends7LinearLayout7{7 77@Inject7Jukebox7jukebox;7 77@Inject7Tweeter7tweeter;7 77 77public7PlayerView(Context7context,7AttributeSet7attrs)7{7 7777super(context,7attrs);7 7777PlayerApp7app7=77 77777777(PlayerApp)7context.getApplicationContext();7

    7777app.inject(this);7 77}! }7
  22. Constructor Injection • @Inject on a single constructor • Dagger

    creates the object
  23. ! ! @Singleton7 public7class7Jukebox7{7 77private7final7AudioStreamer7streamer;7 77 77@Inject7 77public7Jukebox(AudioStreamer7streamer)7{7 7777this.streamer7=7streamer;7 7}7

    }7
  24. 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
  25. AudioStreamer TwitterApi OkHttpClient

  26. ! public7class7CloudModule7{7 77 77public7OkHttpClient7provideHttpClient()7{7 7777return7new7OkHttpClient();7 77}7 ! 777 77TwitterApi7provideTwitterApi(OkHttpClient7client)7{7 7777return7new7TwitterApi(client);7

    77}7 777 77 77AudioStreamer7provideAudioStreamer(OkHttpClient7client)7{7 7777return7new7AudioStreamer(client);7 77}7 }
  27. @Module7 public7class7CloudModule7{7 77 77public7OkHttpClient7provideHttpClient()7{7 7777return7new7OkHttpClient();7 77}7 77 777 77TwitterApi7provideTwitterApi(OkHttpClient7client)7{7 7777return7new7TwitterApi(client);7

    77}7 777 77 77AudioStreamer7provideAudioStreamer(OkHttpClient7client)7{7 7777return7new7AudioStreamer(client);7 77}7 }7
  28. @Module7 public7class7CloudModule7{7 77@Provides7 77public7OkHttpClient7provideHttpClient()7{7 7777return7new7OkHttpClient();7 77}7 77 77@Provides77 77TwitterApi7provideTwitterApi(OkHttpClient7client)7{7 7777return7new7TwitterApi(client);7

    77}7 777 77@Provides7 77AudioStreamer7provideAudioStreamer(OkHttpClient7client)7{7 7777return7new7AudioStreamer(client);7 77}7 }7
  29. @Module7 public7class7CloudModule7{7 77@Provides7@Singleton7 77public7OkHttpClient7provideHttpClient()7{7 7777return7new7OkHttpClient();7 77}7 77 77@Provides7@Singleton77 77TwitterApi7provideTwitterApi(OkHttpClient7client)7{7 7777return7new7TwitterApi(client);7

    77}7 777 77@Provides7@Singleton7 77AudioStreamer7provideAudioStreamer(OkHttpClient7client)7{7 7777return7new7AudioStreamer(client);7 77}7 }7
  30. ! public7class7PlayerApp7extends7Application7{7 77private7ObjectGraph7objectGraph;7 77 77@Override7public7void7onCreate()7{7 7777super.onCreate();7 77777 7777objectGraph7=7ObjectGraph.create(7 77777777new7CloudModule(),7 77777777new7AppModule(this)7

    !!!!);! 77}7 77 77public7<T>7T7inject(T!thing)7{7 7777return7objectGraph.inject(thing);7 77}7 }
  31. Square Style • Modularity via Views! • Injected by Dagger!

    • Navigated by Flow! • Presented and scoped by Mortar ✓ ✓
  32. Play 

  33. Play  Constantinople Sinister Exager… The Booker… Blue Rosebuds Laughing

    Song Bach is Dead Elvis & His Boss Laughing Song
  34. Flow: minimalist backstack Courtesy of Logan Johnson

  35. Flow: minimalist backstack • Stack of POJO “Screens,” like Intents

    or URLs
  36. 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 }
  37. ! 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}
  38. ! ! @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
  39. 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
  40. ! 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
  41. ! 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
  42. ! ! 77/**#Flow.Listener#*/7 77@Override7public7void7go(Backstack7backstack,77 777777Flow.Direction7direction,7Flow.Callback7callback)7{7 7777Object7screen7=7backstack.current().getScreen();7 7777View7newView7=7Layouts.createView(this,7screen);7 77 7777setContentView(newView);7 7777callback.onComplete();7

    77}
  43. 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()
  44. ! ! ##/**#Device#back#button#pressed#*/# 77@Override7public7void7onBackPressed()7{7 7777if7(!flow.goBack())7super.onBackPressed();7 77}7 ! 77@Override77 77public7boolean7onOptionItemSelected(MenuItem!item)7{7 7777//7Android7ActionBar7“Up/Home”7button7pressed.7

    7777if7(item.getItemId()7==7android.R.id.home)7{7 777777return7flow.goUp();7 7777}7 77777 7777return7super.onOptionsItemSelected(item);7 77}7
  45. ! ! @Layout(R.layout.track_view)7 public7class7TrackScreen7implements7HasParent7{7 77public7final7int7albumId;7 77public7final7int7trackId;7 77 77public7TrackScreen(int7albumId,7int7trackId)7{7 7777this.albumId7=7albumId;7 7777this.trackId7=7trackId;7

    77}7 77 77@Override7public7AlbumScreen7getParent()7{7 7777return7new7AlbumScreen(albumId);7 77}7 !
  46. Too Good to Be True? Yeah, probably

  47. Flow: minimalist backstack AppFlow ScreenSwitcher

  48. ! ! public7class7MainActivity7extends7Activity77 7777implements7Flow.Listener7{! ! !!private7final7FlowBundler7flowBundler7=77 7777new7FlowBundler(this)7{
 7777@Override7protected7Backstack7getColdStartBackstack()7{
 777777return7Backstack.single(new7Albumlist());
 7777}


    77};
  49. 77 77 ##/**#E.g.#FrameScreenSwitcherLayout#*/# 77CanShowView7container;7 77 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);7 7777setContentView(R.layout.root_layout);7 7777container7=7(CanShowView)7findViewById(R.id.container);7 77

    7777flowBundler.onCreate(b);7 7777AppFlow.loadInitialScreen(this);7 77}7
  50. ! ! 77/**#Flow.Listener#*/7 77@Override7public7void7go(Backstack7backstack,77 777777Flow.Direction7direction,7Flow.Callback7callback)7{7 ! 7777Screen7screen7=7(Screen)7backstack.current().getScreen();! 7777container.showScreen(screen,7direction,7callback);! }

  51. ! ! ##/**#Device#back#button#pressed#*/# 77@Override7public7void7onBackPressed()7{7 7777if7(!container.onBackPressed())7super.onBackPressed();7 77}7 !

  52. None
  53. Listen Now My Library Playlists Radio Shop

  54. ! ! ! public7class7MainActivity7extends7Activity77 7777implements7Flow.Listener7{! ! 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);! !!!!setContentView(R.layout.root_layout);! res/layout/root_layout.xml

    res/layoutXsw600dpXland/root_layout.xml
  55. ! <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
  56. ! ! public7class7TabletMasterDetailRoot7extends7LinearLayout7 7777implements7HandlesBack,7HandlesUp,7CanShowScreen7{7 ! 77@Override7public7void7showScreen(Screen7screen,77 777777Flow.Direction7direction,7Flow.Callback7callback)7{7 ! 7777navList.updateSelection(screen);7 7777detailContainer.showScreen(screen,7direction,7callback);7

    77}
  57. ! ! ##/**#This#is#how#a#real#Square#app#handles#modals.#*/## 77@Override7public7void7showScreen(Screen7screen,77 7777777final7Flow.Callback7callback)7{7 7777if7(screen7instanceof7ModalScreen)7{7 777777contentFrame.interceptTouches(true);7 777777modalContainer.showScreen(screen,7new7Flow.Callback()7{7 77777777@Override7public7void7onComplete()7{7 7777777777modalContainer.setVisibility(VISIBLE);7

    7777777777callback.onComplete();7 77777777}7 777777});7 777777return;7 7777}7else7if7(screen7instanceof7RequiresMerchant)7{7
  58. Did you see the cheat? @Inject Flow flow;

  59. PlayerApp Tweeter Jukebox AudioStreamer TwitterApi OkHttpClient SharedPreferences

  60. PlayerApp objectGraph JukeBox etc. MainActivity Flow Tweeter @Inject

  61. PlayerApp objectGraph JukeBox etc. MainActivity Flow @Huh? Tweeter @Inject

  62. MainActivity activityGraph rootGraph JukeBox etc. Flow Tweeter @Inject @Inject

  63. ObjectGraph#plus() • Creates child graph, extends the parent with additional

    modules! • Allows creating “scopes” of dependencies
  64. ! ! public7class7MainActivity7extends7Activity7implements7Flow.Listener7{! 77private7FlowBundler7flowBundler7=7...;7 777 77@dagger.Module(7 7777addsTo7=7AppModule.class,7 7777injects7=7{7 777777AlbumView.class,7AlbumListView.class,7TrackView.class7 7777})7

    77public7class7Module7{7 7777@Provides7Flow7provideFlow()7{7 777777return7flowBundler.getFlow();7 7777}7 77}7
  65. 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
  66. ! ! 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
  67. onConflagrationChange()

  68. Play  Play  Play 

  69. Play  Play 

  70. Play  Play  Presenter @Inject

  71. Play  Play  Presenter @Inject

  72. public7class7TrackView7extends7LinearLayout7{7 77@Inject7TrackViewPresenter7presenter;7 77 77@Override7protected7void7onFinishInflate()7{7 7777super.onFinishInflate();7 7777View7playButton7=7findViewById(R.id.play_button);7 7777playButton.setOnClickListener(new7OnClickListener()7{7 777777@Override7public7void7onClick(View7view)7{7 77777777presenter.onPlayClicked();7 777777}7

    7777});7 7777presenter.takeView(this);7 77}7 77 77@Override7protected7void7onDetachedFromWindow()7{7 7777presenter.dropView(this);7 7777super.onDetachedFromWindow();7 77}7 }
  73. ! ! @Singleton7 public7class7TrackViewPresenter7{7 77private7final7Jukebox7jukebox;7 77private7final7Flow7flow;7 77private7final7Tweeter7tweeter;7 77 77@Inject7 77public7TrackViewPresenter(Jukebox7jukebox,7Flow7flow,77

    777777Tweeter7tweeter)7{7 7777this.jukebox7=7jukebox;7 7777this.flow7=7flow;7 7777this.tweeter7=7tweeter;7 77}7
  74. ! ! 7public7void7onPlayClicked()7{7 7777if7(!jukebox.isPlaying())7{7 777777TrackScreen7screen7=7(TrackScreen)77 7777777777flow.getBackstack().current().getScreen();7 ! 777777jukebox.play(screen.albumId,7screen.trackId);7 777777tweeter.tweet("Playing7"7+7jukebox.playing().name());7 7777}7else7{7

    777777jukebox.stop();7 7777}7 77}! }7
  75. Square Style • Modularity via Views! • Injected by Dagger!

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

  77. Constantinople Sinister Exager… The Booker… Blue Rosebuds Laughing Song Bach

    is Dead Elvis & His Boss Laughing Song P P
  78. P Play  P P

  79. P Play 

  80. P Play 

  81. Mortar: managable, testable

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

    nest to arbitrary depths • Each screen can be a Blueprint for its own shortlived scope
  83. ! ! @Layout(R.layout.track_view)7 public7class7TrackScreen7implements7HasParent7{7 77public7final7int7albumId;77 77public7final7int7trackId;7

  84. ! ! @Layout(R.layout.track_view)7@WithModule(TrackScreen.Module.class)7 public7class7TrackScreen7implements7HasParent7{7 77public7final7int7albumId;7 77public7final7int7trackId;7 ! 77@dagger.Module(! !!!!addsTo7=7MainActivity.Module.class,7 7777injects7=7TrackView.class7

    77)7 77class7Module7{7 7777@Provides7TrackScreen7provideScreen()7{77 777777return7TrackScreen.this;!! !!}7 }
  85. @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
  86. @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
  87. ! 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 }
  88. 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
  89. ! ! @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
  90. ! ! @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
  91. ! ! /**#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
  92. 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
  93. ! public7class7PlayerApp7extends7Application7{7 77private7ObjectGraph7objectGraph;7 77 77@Override7public7void7onCreate()7{7 7777super.onCreate();7 77 7777objectGraph7=7ObjectGraph.create(new7AppModule(this));7 77}7 77

    77public7ObjectGraph7getObjectGraph()7{7 7777return7objectGraph;7 77}7 }
  94. ! 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
  95. 777 public7class7MainActivity7extends7Activity7implements7Flow.Listener7{7 77private7ObjectGraph7activityGraph;7 ! 77@Override7protected7void7onCreate(Bundle7b)7{7 7777super.onCreate(b);! !!!!setContentView(R.layout.root_layout);! ! 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 7777}7 77
  96. 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
  97. 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
  98. ! ! 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
  99. Square Style • Modularity via Views! • Injected by Dagger!

    • Navigated by Flow! • Presented and scoped by Mortar ✓ ✓ ✓ ✓
  100. Resources • square.github.io/dagger! • speakerdeck.com/jakewharton/android-apps-with-dagger-devoxx-2013! • github.com/square/flow! • github.com/square/mortar •

    square.com/careers
  101. Resources • square.github.io/dagger! • speakerdeck.com/jakewharton/android-apps-with-dagger-devoxx-2013! • github.com/square/flow! • github.com/square/mortar •

    square.com/careers