Advanced dependency injection concepts and Dagger implementation. Build on your DI knowledge with more complex patterns and frameworks.
This video explains the @Binds annotation in Dagger, which is a more efficient way to bind an interface to its implementation compared to @Provides methods.
// Interface definition public interface UserRepository { User findById(String id); void save(User user); } // Implementation public class SQLUserRepository implements UserRepository { private final Database database; @Inject public SQLUserRepository(Database database) { this.database = database; } @Override public User findById(String id) { // Implementation using database return database.executeQuery("SELECT * FROM users WHERE id = ?", id); } @Override public void save(User user) { // Implementation using database database.executeUpdate("INSERT INTO users VALUES (?, ?, ?)", user.getId(), user.getName(), user.getEmail()); } } // Module with @Binds - more efficient than @Provides @Module public abstract class RepositoryModule { // Bind UserRepository interface to SQLUserRepository implementation @Binds abstract UserRepository bindUserRepository(SQLUserRepository impl); }
This example demonstrates the use of @Binds in a Dagger module to efficiently bind an interface (UserRepository) to its implementation (SQLUserRepository). The @Binds method is abstract, as Dagger only needs to know about the binding relationship, not how to construct the implementation.
This video covers how to create and use Dagger components in different scenarios, including component factories and instance binding in component builders.
// Component with a Builder pattern @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface AppComponent { UserService userService(); // Component.Builder pattern with runtime values @Component.Builder interface Builder { // Bind a module that requires parameters @BindsInstance Builder apiKey(@Named("api-key") String apiKey); // Set a module instance Builder networkModule(NetworkModule module); // Build method returns the component AppComponent build(); } } // Using the component builder public class MyApplication { private AppComponent component; public void onCreate() { // Create network module with custom configuration NetworkModule networkModule = new NetworkModule("https://api.example.com"); // Build component with runtime values component = DaggerAppComponent.builder() .apiKey("my-secret-key") .networkModule(networkModule) .build(); } }
This example shows how to use Dagger's Component.Builder pattern to create a component with runtime values. The @BindsInstance annotation allows binding values directly into the graph, while modules can also be provided as instances when they require custom configuration.
This video explores Dagger subcomponents and custom scopes, which help organize dependencies based on their lifecycles in complex applications.
// Custom scope annotation for user session @Scope @Retention(RetentionPolicy.RUNTIME) public @interface SessionScope {} // Parent component (application-wide singleton scope) @Singleton @Component(modules = {AppModule.class}) public interface AppComponent { // Factory method to create the session subcomponent SessionComponent.Factory sessionComponentFactory(); } // Subcomponent with session scope @SessionScope @Subcomponent(modules = {SessionModule.class}) public interface SessionComponent { // Dependencies provided by this subcomponent UserManager userManager(); ShoppingCart shoppingCart(); // Factory to create this subcomponent @Subcomponent.Factory interface Factory { SessionComponent create(@BindsInstance User user); } } // Usage example public class MyApp { private AppComponent appComponent; private SessionComponent sessionComponent; public void onCreate() { appComponent = DaggerAppComponent.create(); } public void onUserLogin(User user) { // Create session-scoped component when user logs in sessionComponent = appComponent.sessionComponentFactory().create(user); // Get session-scoped dependencies UserManager userManager = sessionComponent.userManager(); ShoppingCart cart = sessionComponent.shoppingCart(); } public void onUserLogout() { // Clear session component on logout sessionComponent = null; } }
This example demonstrates using subcomponents with custom scopes to manage different dependency lifecycles. The @SessionScope indicates dependencies that should live for the duration of a user session. The parent @Singleton component provides application-wide dependencies, while the @SessionScope subcomponent manages session-specific dependencies.
This video brings together the advanced dependency injection concepts covered in this module, presenting a comprehensive view of advanced Dagger usage.
// Complete advanced DI example // Multibinding with a map - useful for plugins or feature modules @Module public abstract class ProcessorsModule { // Bind multiple implementations to a map @Binds @IntoMap @StringKey("json") abstract DataProcessor bindJsonProcessor(JsonProcessor impl); @Binds @IntoMap @StringKey("xml") abstract DataProcessor bindXmlProcessor(XmlProcessor impl); @Binds @IntoMap @StringKey("csv") abstract DataProcessor bindCsvProcessor(CsvProcessor impl); } // Service using map injection public class DataProcessingService { private final Map<String, DataProcessor> processors; @Inject public DataProcessingService(Map<String, DataProcessor> processors) { this.processors = processors; } public void processData(String format, String data) { DataProcessor processor = processors.get(format); if (processor == null) { throw new IllegalArgumentException("No processor found for format: " + format); } processor.process(data); } }
This advanced example shows Dagger's multibinding feature for injecting a map of processors indexed by a key. This pattern is powerful for plugin architectures where different implementations can be selected at runtime. The @IntoMap annotation with key annotations helps Dagger build the map automatically from all the bound implementations.