Sword fighting with Dagger GDG-NYC Jan 2016

Preview:

Citation preview

Sword Fighting with DaggerMike Nakhimovich

Android Engineer NY Times

What is Dagger?

Dagger offers an alternative way to instantiate

and manage your objects through

Dependency Injection

Dagger 2 open sourced by Google

Dagger 1 – Square

Guice – Google (Dagger v.0)

Compile Time Annotation Processing (Fast)

Superpowers

Lazy

Singleton

Provider

Object Scoping (Application/Session/Activity)

Why Dagger?

In the Olden Days...

Old Waypublic class RedditView {

private RedditPresenter presenter;

public RedditView(...) {

Gson gson = new GsonBuilder().create();

RedditApi redditApi = new Retrofit.Builder()

.addConverterFactory(GsonConverterFactory.create(gson))

.build().create(RedditApi.class);

RedditDAO redditDAO = new RedditDAO(redditApi);

presenter=new RedditPresenter(redditDAO);

}

Externalize Object Creation

@Provides

Gson provideGson() {

}

Create a Provider

return new GsonBuilder().create();

}

ModulesA Module is a part of your application that provides

implementations.

@Module public class DataModule {

}

@Provides Gson provideGson() {

return gsonBuilder.create();}

Dependencies@Provides methods can have their own dependencies

@Provides

RedditApi provideRedditApi(Gson gson) {

return new Retrofit.Builder()

.addConverterFactory(

GsonConverterFactory.create(gson))

.build().create(RedditApi.class);

}

Provides not necessaryYou can also annotate constructors directly to register

public class RedditDAO {

@Inject

public RedditDAO(RedditApi api) {

super();

}

}

Provided by Module

Consuming Dependencies

ComponentsA component is a part of your application that consumes

functionality. Built from Module(s)

@Component( modules = {DataModule.class})

public interface AppComponent {

void inject(RedditView a);

}

Register with ComponentActivities/Services/Views register with an instance of a

component

public RedditView(Context context) {

getComponent().inject(this);

}

Injection Fun

Now you can inject any dependencies

managed by the component.

As a field As a Constructor Param

Instantiating Dependencies Old WayRedditPresenter presenter;

public RedditView(Context context) {

super(context);

Gson gson = new GsonBuilder().create();

RedditApi redditApi = new Retrofit.Builder()

.addConverterFactory(GsonConverterFactory.create(gson))

.build()

.create(RedditApi.class);

RedditDAO redditDAO = new RedditDAO(redditApi);

presenter=new RedditPresenter(redditDAO);

}

Once RedditView registers itself with a component,

Dagger will satisfy all managed dependencies with

@Inject annotations

@Inject RedditPresenter presenter;

public RedditView(...) {getComponent().inject(this);

}

Instantiating Dependencies Dagger Way

How does Dagger do it?A static DaggerAppComponent.builder() call creates a Builder instance;

Builder creates an DaggerAppComponent instance;

DaggerAppComponent creates a RedditView_MembersInjector instance;

RedditView_MembersInjectorr uses RedditPresenter_Factory to

instantiate RedditPresenter and injects it into RedditView.

Just as fast as hand written code but with fewer errors

Generated Code@Generated("dagger.internal.codegen.ComponentProcessor")public final class RedditPresenter_Factory implements Factory<RedditPresenter> {private final MembersInjector<RedditPresenter> membersInjector;

public RedditPresenter_Factory(MembersInjector<RedditPresenter> membersInjector) { assert membersInjector != null;this.membersInjector = membersInjector;

}

@Overridepublic RedditPresenter get() { RedditPresenter instance = new RedditPresenter();membersInjector.injectMembers(instance);return instance;

}

public static Factory<RedditPresenter> create(MembersInjector<RedditPresenter> membersInjector) { return new RedditPresenter_Factory(membersInjector);

}}

Dependency ChainThe Presenter has its own dependency

public class RedditPresenter {

@Inject RedditDAO dao;

}

Dependency ChainWhich has its own dependency

public class RedditDAO {

private final RedditApi api;

@Inject

public RedditDAO(RedditApi api) {

this.api = api;

}

Dagger Eliminates “Passing Through” Constructor Arguments

Old WayRedditPresenter presenter;

public RedditView(...) {

super(context);

Gson gson = new GsonBuilder().create();

RedditApi redditApi = new Retrofit.Builder()

.addConverterFactory(GsonConverterFactory.create(gson))

.build()

.create(RedditApi.class);

RedditDAO redditDAO = new RedditDAO(redditApi);

presenter=new RedditPresenter(redditDAO);

}

Dagger Eliminates “Passing Through” Constructor Arguments

Dagger Way:Inject ONLY the dependencies each

object needs

Type of Injections

Dagger has a few types of injection:

Direct

Lazy

Provider

Direct

When instance is created also create the

dependency.

public class RedditPresenter {

@Inject RedditDAO dao;

}

Lazy

Do not instantiate until .get() is called.

public class RedditPresenter {

@Inject Lazy<RedditDAO> dao;

}

Useful to keep startup time down

ProviderProvider<T> allows you to inject multiple

instances of same object by calling .get()

public class RedditPresenter {

@Inject Provider<RedditDAO>

dao;

}

Singletons Old Waypublic class MainActivity extends Activity {

SharedPreferences pref;

Gson gson;

ServerAPI api;

onCreate(...) {

MyApp app = (MyApp)getContext().getApplicationContext();

pref = app.getSharedPreferences();

gson = app.getGson();

api = app.getApi();

Why is MYApp managing all singletons? :-(

Singletons Dagger Way

Define objects to be Singletons

@Singleton @Provides

Gson provideGson() {

return gsonBuilder.create();

}

Singletons Dagger Way

Define objects to be Singletons

@Singleton

public class RedditDAO{

@Inject

public RedditDAO() {

}

Singleton Management

@Singleton = Single instance per Component

@Singleton

@Component( modules = {DataModule.class})

public interface AppComponent {

void inject(RedditView a);

}

Scopes

Singleton is a scope annotation

@Scope

@Documented

@Retention(RUNTIME)

public @interface Singleton {}

We can create custom scope annotations

Subcomponent

Creating a Subcomponent with a scope

@Subcomponent(modules = {

ActivityModule.class})

@ActivityScope

public interface ActivityComponent

Subcomponent

plussing your component into another

component will inherit all objects

Injecting ActivitiesComponents scoped (recreated) with each activity allows us to do silly things

like injecting an activity into scoped objects:

@ScopeActivity

public class Toaster {

@Inject Activity activity;

private static void showToast() {

Toast.makeText(activity, message).show();

}}

Inject a Scoped Object@Inject

SnackbarUtil snackbarUtil;

there’s no need to pass activity into presenter and then

into the SnackBarMaker

objects can independently satisfy their own

dependencies.

Scoped If you try to inject something into an object with a

different scope, Dagger with give compilation error.

Scopes let objects “share” the same instance of anything in

the scope. ToolbarPresenters, IntentHolders etc.

Scopes cont’dScopes empower “activity singletons” that you

can share and not worry about reinstantiating

for each activity.

Dagger @ The New York Times

How we leveraged scope at The Times:

Inject activity intents into fragments 2 layers down

Create a toolbar presenter, each activity and its views get

access to the same presenter

Inject an alerthelper/snackbar util that can show alerts.

Dagger lets us decompose our activities

Dagger @ The New York TimesWe can inject something that is being provided

by a module in library project.

A/B Module within A/B Project provides

injectable A/B manager

API project owns API module etc. Main Project

creates a component using all the modules

Dagger @ The New York TimesWe can inject different implementations for

same interface spread across build variants

using a FlavorModule.

Amazon Flavor provides module with Amazon

Messaging

Google Flavor provides module with Google

Messaging

Sample Project

https://github.com/digitalbuddha/StoreDemo

Come work with me

http://developers.nytimes.com/careers