Intro To Dependency Injection
Or Bar
- What is a Dependency?
- Dependency Injection
- Dependency Inversion Principle
- Dependency Injection Frameworks
Agenda
A depends on B
public class Example { DatabaseHelper mDatabaseHelper; public Example() { mDatabaseHelper = new DatabaseHelper(); } public void doStuff() { ... mDatabaseHelper.loadUsers(); ... } }
- Dependency injection is a software design pattern that implements inversion of control for resolving dependencies
- An injection is the passing of a dependency to a dependent object to use
public class Example { DatabaseHelper mDatabaseHelper; public Example() { mDatabaseHelper = new DatabaseHelper(); } }
public class Example { DatabaseHelper mDatabaseHelper; public Example() { mDatabaseHelper = new DatabaseHelper(); } }
public class Example { DatabaseHelper mDatabaseHelper; public Example(DatabaseHelper databaseHelper) { mDatabaseHelper = databaseHelper; }}
public class Example { DatabaseHelper mDatabaseHelper; public Example() { mDatabaseHelper = new DatabaseHelper(); } }
public class Example { DatabaseHelper mDatabaseHelper;
public Example() { mDatabaseHelper = new DatabaseHelper(); } }
public class Example { DatabaseHelper mDatabaseHelper; public Example(DatabaseHelper databaseHelper) { mDatabaseHelper = databaseHelper; }}
public class Example { DatabaseHelper mDatabaseHelper; public Example(DatabaseHelper databaseHelper) { mDatabaseHelper = databaseHelper; } }
public class Example { DatabaseHelper mDatabaseHelper; public Example() { } public void setDatabaseHelper(DatabaseHelper databaseHelper) { mDatabaseHelper = databaseHelper; }}
- Shared dependencies
- Configure dependencies externally
- Separation of modules
- Inherent testability
Why?
public class ExampleTest { @Mock DatabaseHelper mockDatabaseHelper; @Test public void testExample_doStuff() { Example example = new Example(mockDatabaseHelper); example.doStuff(); mockDatabaseHelper.AssertGetDataWasCalled(); }}
public class Example { DatabaseHelper mDatabaseHelper; public Example(DatabaseHelper databaseHelper) { mDatabaseHelper = databaseHelper; } public void doStuff() { ... mDatabaseHelper.loadUsers(); ... }
}
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
Dependency Inversion Principle
A depends on B
A is coupled to B
- Bare bones ebook reader
- Supports PDF files
- Prints book contents to screen
Ebook Reader
CoolEBookReader
PDFReader DisplayPrinter
public class CoolEBookReader { public void loadPage(String bookUri, int PageNumber) { String pdfContent = getPdfContent(bookUri, PageNumber); displayPage(pdfContent); } }
public class CoolEBookReader { enum BookType { PDF, EPUB } public void loadPage(BookType bookType, String bookUri, int PageNumber) { String pageContent; if (bookType == BookType.PDF) { pageContent = getPdfContent(bookUri, PageNumber); } else if (bookType == BookType.EPUB) { pageContent = getEpubContent(bookUri, PageNumber); } else { throw new IllegalArgumentException("Unknown book type"); } displayPage(pageContent); } }
public class CoolEBookReader { enum BookType { PDF, EPUB } enum PrinterType { SCREEN, VOICE } public void loadPage(BookType bookType, PrinterType printerType, String bookUri, int PageNumber) { String pageContent; if (bookType == BookType.PDF) { pageContent = getPdfContent(bookUri, PageNumber); } else if (bookType == BookType.EPUB) { pageContent = getEpubContent(bookUri, PageNumber); } else { throw new IllegalArgumentException("Unknown book type"); } if (printerType == PrinterType.SCREEN) { displayPage(pageContent); } else if (printerType == PrinterType.VOICE) { readAloudPage(pageContent); } else { throw new IllegalArgumentException("Unknown printer type"); } } }
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
Dependency Inversion
CoolEBookReader
PDFReader DisplayPrinter
<<Interface>> Reader
<<Interface>> Printer
interface Reader { String read(String bookUri, int pagNumber); } interface Printer { void print(String pageContent);}
public class CoolEBookReader { public void loadPage(Reader reader, Printer printer, String bookUri, int pageNumber) { String pageContent = reader.read(bookUri, pageNumber); printer.print(pageContent); } }
interface Reader { String read(String bookUri, int pagNumber); } interface Printer { void print(String pageContent);}
public class CoolEBookReader { public void loadPage(Reader reader, Printer printer, String bookUri, int pageNumber) { String pageContent = reader.read(bookUri, pageNumber); printer.print(pageContent); } }
public class CoolEBookReader { public void loadPage(String bookUri, int PageNumber) { String pdfContent = getPdfContent(bookUri, PageNumber); displayPage(pdfContent); } }
public class CoolEBookReaderTest { @Mock Reader mockReader; @Mock CoolEBookReader.Printer mockPrinter; @Test public void TestExample_loadPage() { CoolEBookReader coolEBookReader = new CoolEBookReader(); coolEBookReader.loadPage(mockReader, mockPrinter, "", 1); // Assert Reader.read() is called // Assert Printer.print() is called } }
- Dependency Injection
- Dependency Inversion
- Where are these dependencies come from
What’s next?
- Handles object creation
- Reduces boilerplate code
- A central location for organizing dependencies
- Implement common patterns natively (singleton, lazy loading, etc)
Why use a framework?
- Dagger 1
- Dagger 2
- Guice and RoboGuice
- PicoContainer
- Spring
- Many, many, many, many more options
Common Frameworks
History lesson of DI on Android- RoboGuice - 2009
- Spring for Android - 2012
- Dagger 1 - 2013
- Dagger 2 - 2015
- Tiger - 2016
- @javax.inject.Inject
- @Module
- @Provides
- @Component
Dagger uses annotations
public class ConnectionHelper { private Context mContext; public ConnectionHelper(Context context) { mContext = context; } public boolean isConnected() { ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); return connectivityManager.getActiveNetworkInfo().isConnected(); } }
ConnectionHelper connectionHelper = new ConnectionHelper(context);boolean isConnected = connectionHelper.isConnected();
public class ConnectionHelper { private Context mContext; @Inject public ConnectionHelper(Context context) { mContext = context; } public boolean isConnected() { ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); return connectivityManager.getActiveNetworkInfo().isConnected(); }}
@Inject
@Modulepublic class AppModule { }
@Module
@Provides@Modulepublic class AppModule { private final Context mContext; public AppModule(Context context) { mContext = context; }
@Provides public Context providesContext() { return mContext; }
@Provides public ConnectionHelper providesConnectionHelper(Context context) { return new ConnectionHelper(context); }}
@Component(modules = {AppModule.class})public interface AppComponent { void inject(MainActivity activity);}
@Component
public class App extends Application { private AppComponent component; @Override public void onCreate() { super.onCreate(); AppComponent component = DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); } public AppComponent getComponent() { return component; }}
public class App extends Application { private AppComponent component; @Override public void onCreate() { super.onCreate(); AppComponent component = DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); } public AppComponent getComponent() { return component; }}
public class MainActivity extends AppCompatActivity { @Inject ConnectionHelper mConnectionHelper; @Override protected void onCreate(Bundle savedInstanceState) { ((App) getApplication()).getComponent().inject(this); boolean isConnected = mConnectionHelper.isConnected(); textView.setText(isConnected ? "Connected" : "Not Connected"); }}
public class MainActivity extends AppCompatActivity { @Inject ConnectionHelper mConnectionHelper; @Override protected void onCreate(Bundle savedInstanceState) { ((App) getApplication()).getComponent().inject(this); boolean isConnected = mConnectionHelper.isConnected(); textView.setText(isConnected ? "Connected" : "Not Connected"); }}
@Inject - again
- More annotations
- Scopes
- Lazy injections
- others
Fun stuff
@Provides@Singletonpublic Context providesContext() { return context; }
@Singleton
@Singleton@Component(modules = {AppModule.class})public interface AppComponent { void inject(MainActivity activity); }
@Provides@Reusablepublic ConnectionHelper providesConnectionHelper(Context context) { return new ConnectionHelper(context);}
@Reusable
@Singleton @Reusable@Component(modules = {AppModule.class})public interface AppComponent {}
@InjectLazy<ConnectionHelper> mConnectionHelper; ... mConnectionHelper.get().isConnected();
Lazy and Provider Injections
@InjectProvider<ConnectionHelper> mConnectionHelper; ... mConnectionHelper.get().isConnected();
@Target(ANNOTATION_TYPE) @Retention(RUNTIME) @Documentedpublic @interface Qualifier {}
@Qualifier
@Qualifier@Documented@Retention(RUNTIME) public @interface Named {}
@Provides @Singleton @Named(“Adapter 1”) public RestAdapter providesRestAdapter1() { return …; }
@Qualifier
@Provides @Singleton @Named(“Adapter 2”) public RestAdapter providesRestAdapter2() { return …; }
@Qualifier@Inject @Named(“Adapter 1”) RestAdapter mRestAdapter;
Subcomponents and Scopes
- Subcomponents allow you to combine different modules at runtime
- Scopes let you define the lifetime of a dependency
Testing and dagger
- Constructors used for injections can be used for testing
- Test Modules for espresso
Questions
- @or_bar
- orbar1.tumblr.com
Top Related