
Multi-scoping Dagger components
Reusability is one of the many attributes that are said to contribute to a high quality code base. Dagger is in many aspects a tool which helps a lot with writing reusable code.
In this post I would like to discuss a scoping feature of Dagger 2 which is not very well known but does help a lot if you have a specific module that is reused a couple of times in the app. I am not sure if this feature has a name, but I personally call it multi-scoping of components.
The problem
Let’s say you are writing an app where a video player appears in many parts of the app. It contains a lot of different screens which all use a VideoPlayer
class, which has a VideoView
dependency, which is an interface defined by the video player. This class is being provided in a VideoPlayerModule
and that will be included in every dagger component that needs to use the player feature. Let’s say we have a VideoActivity
which uses this and a VideoFragment
which can be included in other activities. Both the Activity and the fragment implement the VideoView interface.
public abstract class VideoPlayerModule {
@Provides
static VideoPlayer provideVideoPlayer(VideoView view) {
return new VideoPlayer(view);
}
}
With VideoActivity using it in a component that has ActivityScope
.
@Component(modules = VideoActivityModule.class)
@ActivityScope
public interface VideoActivityComponent {
void inject(VideoActivity activity);
}@Module(includes = VideoPlayerModule.class)
public abstract class VideoActivityModule {
@Binds abstract VideoView bindView(VideoActivity activity);
}
And VideoFragment using it in a component that has FragmentScope
.
@Subcomponent(modules = VideoFragmentModule.class)
@FragmentScope
public interface VideoFragmentComponent {
void inject(VideoFragment fragment);
}@Module(includes = VideoPlayerModule.class)
public abstract class VideoFragmentModule {
@Binds abstract VideoView provideView(VideoFragment fragment);
}
This works fine if you only inject the VideoPlayer
once per component. However, it is currently un-scoped which is not what we would want for a video player.
We can not add an ActivityScope
to VideoPlayerModule.provideVideoPlayer()
though because it is also consumed in the fragment module. We can also not give it FragmentScope
because then it can not be referenced from the ActivityPlayerComponent
which is ActivityScope
.
One solution might be to move the Provides
methods to the activity and fragment module, but that is not sustainable when you need to scope more classes from the VideoPlayerModule
.
Multi-scoping components
After some trial and error I found out that you can actually have multiple scopes on a one component. So the trick here is that you can define a scope @PlayerScope
which you can use to scope any classes that are provided from the VideoPlayerModule
and the you can annotate your Activity and Fragment component like this:
@Component(modules = VideoActivityModule.class)
@ActivityScope
@PlayerScope
public interface VideoActivityComponent {
void inject(VideoActivity activity);
}@Subcomponent(modules = VideoActivityModule.class)
@FragmentScope
@PlayerScope
public interface VideoFragmentComponent {
void inject(VideoFragment activity);
}
Et, voila! Now you essentially have defined that within the VideoActivityComponent
the lifetime of PlayerScope
objects is equal to that of ActivityScope
objects, and within the VideoFragmentComponent
everything withPlayerScope
is of equal lifetime as FragmentScope
.
Note that this also works when you are using dagger-android
when using ContributesAndroidInjector
. You can do the following:
public abstract class ApplicationModule { @ContributesAndroidInjector(modules = PlayerActivityModule.class)
@PlayerScope
@ActivityScope
abstract PlayerActivity contributeActivityInjector();}
I hope you find this tip useful and till next time. and thanks Billy Chang for inspiring me to write this short post.