Dagger- Should we create each component and module for each Activity/ Fragment
Solution 1:
Declaring a separate module for each Activity
is not a good idea at all. Declaring separate component for each Activity
is even worse. The reasoning behind this is very simple - you don't really need all these module/components (as you have already seen by yourself).
However, having just one component that is tied to Application
's life-cycle and using it for injection into all Activities
is also not the optimal solution (this is your friend's approach). It is not optimal because:
- It restricts you to just one scope (
@Singleton
or a custom one) - The only scope you're restricted to makes the injected objects "application singletons", therefore mistakes in scoping or incorrect usage of scoped objects can easily cause global memory leaks
- You'll want to use Dagger2 in order to inject into
Services
too, butServices
can require different objects thanActivities
(e.g.Services
don't need presenters, don't haveFragmentManager
, etc.). By using a single component you loose the flexibility of defining different object graphs for different components.
So, a component per Activity
is an overkill, but single component for the entire application is not flexible enough. The optimal solution is in between these extremes (as it usually is).
I use the following approach:
- Single "application" component that provides "global" objects (e.g. objects that hold global state which is shared between all components in the application). Instantiated in
Application
. - "Controller" subcomponent of "application" component that provides objects which are required by all user-facing "controllers" (in my architecture these are
Activities
andFragments
). Instantiated in eachActivity
andFragment
. - "Service" subcomponent of "application" component that provides objects which are required by all
Services
. Instantiated in eachService
.
Following is an example of how you could implement the same approach.
Edit July 2017
I published a video tutorial that shows how to structure Dagger dependency injection code in Android application: Android Dagger for Professionals Tutorial.
Edit February 2018
I published a complete course about dependency injection in Android.
In this course I explain the theory of dependency injection and show how it emerges naturally in Android application. Then I demonstrate how Dagger constructs fit into the general dependency injection scheme.
If you take this course you will understand why the idea of having a separate definition of module/component for each Activity/Fragment is basically flawed in the most fundamental way.
Such an approach causes the structure of presentation layer from "Functional" set of classes to be mirrored into the structure of "Construction" set of classes, thus coupling them together. This goes against the main objective of dependency injection which is to keep the "Construction" and "Functional" sets of classes disjoint.
Application scope:
@ApplicationScope
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
// Each subcomponent can depend on more than one module
ControllerComponent newControllerComponent(ControllerModule module);
ServiceComponent newServiceComponent(ServiceModule module);
}
@Module
public class ApplicationModule {
private final Application mApplication;
public ApplicationModule(Application application) {
mApplication = application;
}
@Provides
@ApplicationScope
Application applicationContext() {
return mApplication;
}
@Provides
@ApplicationScope
SharedPreferences sharedPreferences() {
return mApplication.getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE);
}
@Provides
@ApplicationScope
SettingsManager settingsManager(SharedPreferences sharedPreferences) {
return new SettingsManager(sharedPreferences);
}
}
Controller scope:
@ControllerScope
@Subcomponent(modules = {ControllerModule.class})
public interface ControllerComponent {
void inject(CustomActivity customActivity); // add more activities if needed
void inject(CustomFragment customFragment); // add more fragments if needed
void inject(CustomDialogFragment customDialogFragment); // add more dialogs if needed
}
@Module
public class ControllerModule {
private Activity mActivity;
private FragmentManager mFragmentManager;
public ControllerModule(Activity activity, FragmentManager fragmentManager) {
mActivity = activity;
mFragmentManager = fragmentManager;
}
@Provides
@ControllerScope
Context context() {
return mActivity;
}
@Provides
@ControllerScope
Activity activity() {
return mActivity;
}
@Provides
@ControllerScope
DialogsManager dialogsManager(FragmentManager fragmentManager) {
return new DialogsManager(fragmentManager);
}
// @Provides for presenters can be declared here, or in a standalone PresentersModule (which is better)
}
And then in Activity
:
public class CustomActivity extends AppCompatActivity {
@Inject DialogsManager mDialogsManager;
private ControllerComponent mControllerComponent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getControllerComponent().inject(this);
}
private ControllerComponent getControllerComponent() {
if (mControllerComponent == null) {
mControllerComponent = ((MyApplication)getApplication()).getApplicationComponent()
.newControllerComponent(new ControllerModule(this, getSupportFragmentManager()));
}
return mControllerComponent;
}
}
Additional information on dependency injection:
Dagger 2 Scopes Demystified
Dependency Injection in Android
Solution 2:
Some of the best examples of how to organise your components, modules, and packages can be found in the Google Android Architecture Blueprints Github repo here.
If you examine the source code there, you can see there is one single app-scoped Component (with a lifecycle of the duration of the whole app) and then separate Activity-scoped Components for the Activity and Fragment corresponding to a given functionality in a project. For example, there are the following packages:
addedittask
taskdetail
tasks
Inside each package there is a module, component, presenter etc. For instance, inside taskdetail
there are the following classes:
TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java
The advantage of organising this way (rather than grouping all of the activities in one component or module) is that you can take advantage of Java accessibility modifiers and fulfil Effective Java item 13. In other words, the functionally grouped classes will be in the same package and you can take advantage of protected
and package-private
accessibility modifiers to prevent unintended usages of your classes.
Solution 3:
First option creates a subscoped component for each activity, where the activity is able to create subscoped components that only provide the dependency (presenter) for that particular activity.
Second option creates a single @Singleton
component that is able to provide the presenters as unscoped dependencies, meaning when you access them, you create a new instance of the presenter each time. (No, it doesn't create a new instance until you request one).
Technically, neither approach is worse than the other. The first approach doesn't separate presenters by feature, but by layer.
I've used both, they both work and both make sense.
The only disadvantage of the first solution (if you're using @Component(dependencies={...}
instead of @Subcomponent
) is that you need to make sure it's not the Activity that creates its own module internally, because then you cannot replace module method implementations with mocks. Then again, if you use constructor injection instead of field injection, you can just create the class directly with constructor, directly giving it mocks.
Solution 4:
Use Provider<"your component's name">
instead of simple components implementation to avoid memory leaks and creating tons of useless components. Therefore your components will be created by lazy when you call get() method since you don't provide an instance of component but just provider instead. Thus your presenter is gonna be applied if .get() of provider was called. Read about Provider here and apply this.
(Official Dagger documentation)
And other great way is to use multibinding. In accordance with it you should bind your presenters into map and create them through providers when you need. (here is docs about multibinding)