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

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. Under Square’s Hood
    Ray Ryan

    View full-size slide

  2. Square Style
    • Modularity viaViews
    • Injected by Dagger
    • Navigated by Flow
    • Presented and scoped by Mortar
    • Framework

    View full-size slide

  3. Your Inalienable Right!
    to Modularity
    android.app.Activity
    android.app.Fragment
    android.view.View

    View full-size slide

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

    View full-size slide

  5. !
    !
    !
    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
    !!!!77777777android:layout_width="match_parent"7android:layout_height="0dp"7
    77777777android:id="@+id/imageView"7
    77777777android:layout_weight="1"/>7
    !!!!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
    7

    View full-size slide

  6. !
    !
    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

    View full-size slide

  7. !
    !
    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}

    View full-size slide

  8. !
    !
    public7class7PlayerApp7extends7Application7{7
    77public7Jukebox7getJukebox()7{7
    7777return7jukebox;7
    77}7
    77
    77public7Tweeter7getTweeter()7{7
    7777return7tweeter;7
    77}!
    }7

    View full-size slide

  9. Play

    PlayerApp
    Jukebox
    Tweeter

    View full-size slide

  10. PlayerApp
    Tweeter
    TwitterApi
    Play

    Jukebox
    AudioStreamer

    View full-size slide

  11. PlayerApp
    Play

    Tweeter
    Jukebox
    AudioStreamer
    TwitterApi
    OkHttpClient
    SharedPreferences

    View full-size slide

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

    View full-size slide

  13. !
    !
    !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}

    View full-size slide

  14. That could turn into a lot of code

    View full-size slide

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

    View full-size slide

  16. Dagger: @Inject Dependencies
    • Guicecc
    code generated, Android friendly, fast fast
    • @Inject
    “I want…”
    • @Module + @Provides
    “I make…”

    View full-size slide

  17. Requesting Dependencies (“I want…”)
    • @Inject annotation required
    • Field injection
    • Constructor injection

    View full-size slide

  18. Field Injection
    • @Inject on fields which are dependencies
    • For the stuff created by Android — Activity,View
    • Self inflicted

    View full-size slide

  19. !
    !
    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

    View full-size slide

  20. !
    !
    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

    View full-size slide

  21. Constructor Injection
    • @Inject on a single constructor
    • Dagger creates the object

    View full-size slide

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

    View full-size slide

  23. 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

    View full-size slide

  24. AudioStreamer
    TwitterApi
    OkHttpClient

    View full-size slide

  25. !
    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
    }

    View full-size slide

  26. @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

    View full-size slide

  27. @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

    View full-size slide

  28. @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

    View full-size slide

  29. !
    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
    77public77T7inject(T!thing)7{7
    7777return7objectGraph.inject(thing);7
    77}7
    }

    View full-size slide

  30. Square Style
    • Modularity via Views!
    • Injected by Dagger!
    • Navigated by Flow!
    • Presented and scoped by Mortar


    View full-size slide

  31. Play

    Constantinople
    Sinister Exager…
    The Booker…
    Blue Rosebuds
    Laughing Song
    Bach is Dead
    Elvis & His Boss
    Laughing Song

    View full-size slide

  32. Flow: minimalist backstack
    Courtesy of Logan Johnson

    View full-size slide

  33. Flow: minimalist backstack
    • Stack of POJO “Screens,” like Intents or URLs

    View full-size slide

  34. 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
    }

    View full-size slide

  35. !
    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}

    View full-size slide

  36. !
    !
    @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

    View full-size slide

  37. 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

    View full-size slide

  38. !
    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

    View full-size slide

  39. !
    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

    View full-size slide

  40. !
    !
    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}

    View full-size slide

  41. 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()

    View full-size slide

  42. !
    !
    ##/**#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

    View full-size slide

  43. !
    !
    @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
    !

    View full-size slide

  44. Too Good to Be True?
    Yeah, probably

    View full-size slide

  45. Flow: minimalist backstack
    AppFlow
    ScreenSwitcher

    View full-size slide

  46. !
    !
    public7class7MainActivity7extends7Activity77
    7777implements7Flow.Listener7{!
    !
    !!private7final7FlowBundler7flowBundler7=77
    7777new7FlowBundler(this)7{

    7777@Override7protected7Backstack7getColdStartBackstack()7{

    777777return7Backstack.single(new7Albumlist());

    7777}

    77};

    View full-size slide

  47. 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

    View full-size slide

  48. !
    !
    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);!
    }

    View full-size slide

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

    View full-size slide

  50. Listen Now
    My Library
    Playlists
    Radio
    Shop

    View full-size slide

  51. !
    !
    !
    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

    View full-size slide

  52. !
    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
    77777777android:id=“@+id/nav_list"7
    777777android:layout_weight="20"7
    777777android:layout_width="0dp"7
    777777android:layout_height="match_parent"7
    777777/>7
    77777777android:id="@+id/detail"7
    777777android:layout_weight="80"7
    777777android:layout_width="0dp"7
    777777android:layout_height="match_parent"7
    777777/>7
    7

    View full-size slide

  53. !
    !
    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}

    View full-size slide

  54. !
    !
    ##/**#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

    View full-size slide

  55. Did you see the cheat?
    @Inject Flow flow;

    View full-size slide

  56. PlayerApp
    Tweeter
    Jukebox
    AudioStreamer
    TwitterApi
    OkHttpClient
    SharedPreferences

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  60. ObjectGraph#plus()
    • Creates child graph, extends the parent with additional modules!
    • Allows creating “scopes” of dependencies

    View full-size slide

  61. !
    !
    public7class7MainActivity7extends7Activity7implements7Flow.Listener7{!
    77private7FlowBundler7flowBundler7=7...;7
    777
    [email protected](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

    View full-size slide

  62. 777
    !
    77private7ObjectGraph7activityGraph;7
    !
    77public77T7inject(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

    View full-size slide

  63. !
    !
    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

    View full-size slide

  64. onConflagrationChange()

    View full-size slide

  65. Play

    Play

    Play

    View full-size slide

  66. Play

    Play

    Presenter
    @Inject

    View full-size slide

  67. Play

    Play

    Presenter
    @Inject

    View full-size slide

  68. 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
    }

    View full-size slide

  69. !
    !
    @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

    View full-size slide

  70. !
    !
    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

    View full-size slide

  71. Square Style
    • Modularity via Views!
    • Injected by Dagger!
    • Navigated by Flow!
    • Presented and scoped by Mortar



    View full-size slide

  72. Constantinople
    Sinister Exager…
    The Booker…
    Blue Rosebuds
    Laughing Song
    Bach is Dead
    Elvis & His Boss
    Laughing Song
    P
    P

    View full-size slide

  73. Mortar: managable, testable

    View full-size slide

  74. Mortar: managable, testable
    • MortarScope enshrines, simplifies ObjectGraph.plus()
    • Scopes nest to arbitrary depths
    • Each screen can be a Blueprint for its own shortlived scope

    View full-size slide

  75. !
    !
    @Layout(R.layout.track_view)7
    public7class7TrackScreen7implements7HasParent7{7
    77public7final7int7albumId;77
    77public7final7int7trackId;7

    View full-size slide

  76. !
    !
    @Layout(R.layout.track_view)7@WithModule(TrackScreen.Module.class)7
    public7class7TrackScreen7implements7HasParent7{7
    77public7final7int7albumId;7
    77public7final7int7trackId;7
    !
    [email protected](!
    !!!!addsTo7=7MainActivity.Module.class,7
    7777injects7=7TrackView.class7
    77)7
    77class7Module7{7
    7777@Provides7TrackScreen7provideScreen()7{77
    777777return7TrackScreen.this;!!
    !!}7
    }

    View full-size slide

  77. @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

    View full-size slide

  78. @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

    View full-size slide

  79. !
    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
    }

    View full-size slide

  80. 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

    View full-size slide

  81. !
    !
    @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

    View full-size slide

  82. !
    !
    @Singleton77
    public7class7TrackViewPresenter7extends7ViewPresenter7{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

    View full-size slide

  83. !
    !
    /**#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

    View full-size slide

  84. 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

    View full-size slide

  85. !
    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
    }

    View full-size slide

  86. !
    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

    View full-size slide

  87. 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

    View full-size slide

  88. 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

    View full-size slide

  89. 777
    !
    !
    77public77T7inject(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

    View full-size slide

  90. !
    !
    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

    View full-size slide

  91. Square Style
    • Modularity via Views!
    • Injected by Dagger!
    • Navigated by Flow!
    • Presented and scoped by Mortar




    View full-size slide

  92. 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

    View full-size slide

  93. 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

    View full-size slide