Dagger 2 implementation
Before start Reading this code post...
Maybe I'm going to skip some basic concepts of Dagger 1 and 2 and matters about Dagger 2 code generation but I want to be focused in this implementation and the posts are taking already too long... Anyway I will give you some interesting references at the end of the post. But something almost compulsory, it's to read my previous post: Dagger 2... Why not? because this implementation is based on the experience I told there.
Project description
Basically this is a dummy application really poor in UI. On the other hand project structure is a little complex comparing with UI.
The app starts a main Activity which is the container of some fragments and dependency injection is resolved in those UI classes. Pay special attention to "Section Concept".
At left you can see the package explorer showing all the classes distributed inside his corresponding package.
Injected classes are inside test package, A, B, C, D, E, NotScopedProperty and SectionProperty classes do almost nothing and the only purpose of their existence is checking if dependency injection works, how it works and some matters about singletons and scopes. We can check all those things with empty objects...
There is an implementation of a logger Log4MeImpl and its interface that will be injected as well, doing something more interesting. There is a list of injections as a singleton in the app where the app registers all the injected objects with the aim of print that list in the last loaded fragment. Then we will get the result of the injection, in order to check if injected object where living at the expected context.
Injected classes are inside test package, A, B, C, D, E, NotScopedProperty and SectionProperty classes do almost nothing and the only purpose of their existence is checking if dependency injection works, how it works and some matters about singletons and scopes. We can check all those things with empty objects...
There is an implementation of a logger Log4MeImpl and its interface that will be injected as well, doing something more interesting. There is a list of injections as a singleton in the app where the app registers all the injected objects with the aim of print that list in the last loaded fragment. Then we will get the result of the injection, in order to check if injected object where living at the expected context.
Let's start
The first interesting thing is creating the first component, the App component. This is not depending on any other and it can be created calling "create()" method from the dagger component generated class. Calling "create" method if necessary modules of AppComponent will be instantiated by using no arg constructor. That means, if you need some arg in your modules then you must call:
DaggerAppComponent.builder().appModule(new AppModule(YOUR_ARGS)).build()
App.class
public class App extends Application {
private AppComponent appComponent;
.....
private void initDI() {
appComponent = DaggerAppComponent.create();
/** DaggerAppComponent.create() is equivalent to :
* DaggerAppComponent.builder().appModule(new AppModule()).build()
* when module has not arguments
**/
appComponent.inject(this);
}
....
public AppComponent getAppComponent() {
return appComponent;
}
}
AppComponent.class
@Singleton @Component(modules = AppModule.class)
public interface AppComponent {
void inject(App app);
A provideA();
List<String> provideDiInjectionHistory();
Log4Me provideLog4Me();
}
Here AppComponent has some interesting information to tell us:
- @Singleton annotation means this is the scope and injections provided here and annotated with @Singleton will have a unique instance on the app.
- inject() method is the replacement for the old module option injects={}
- AppComponent serves 3 objects. This is because we will create a relationship using ActivityComponent and we want to inject those three objects.
Now, here you can see the provision of the served objects in AppComponent:
AppModule.class:
@Module(includes = IncludedModule.class)
public class AppModule {
@Singleton @Provides List<String> provideDiInjectionHistory(){
return new ArrayList<>();
}
}
Note includes module option is still useful!!
IncludedModule.class:
@Module
public class IncludedModule {
@Provides @Singleton A provideA(){
return new A();
}
@Provides @Singleton Log4Me provideLog4Me(){
return new Log4MeImpl();
}
}
Note as well that @Singleton annotation has to be present in the corresponding provisioning method.
Let's go now with something a little bit more difficult: Abstract BaseInjectionActivity
BaseInjectionActivity.class
public abstract class BaseInjectionActivity<T> extends AppCompatActivity{
protected T activityComponent;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initDI();
}
protected abstract void initDI();
public AppComponent getAppComponent() {
AppComponent appComponent = ((App) getApplication()).getAppComponent();
return appComponent;
}
public Object getActivityComponent() {
return activityComponent;
}
}
I did my best in order to hide DI config for the rest of activities bus it was unable for me... the minimal flexibility of dagger generated code makes compulsory the execution of the following lines in your final activity...
@Override protected void initDI() {
activityComponent = DaggerMainActivityComponent.builder().appComponent(getAppComponent())
.activityModule(new ActivityModule(this))
.mainActivityModule(new MainActivityModule(this)).build();
activityComponent.injectActivity(this);
}
you can't use superclasses... it's a pity. At least the reference to the component is a generic object parametrized and all stuff about keeping the reference and getting it can be hidden.
Good, already seen MainActivityComponent initialization let's have a look... Component needs modules, the first one is about common provisions of all activities and the second one is specific for this activity. I took this idea from some implementations of Dagger 1 and I thought about keeping it as a good practice.
MainActivity component needs as well the AppComponent for his initialization, that is because MainActivityComponent has AppComponent as a dependency. You can have so many dependencies and modules as you need for your component initialization...
MainActivityComponent.class
@PerActivity
@Component(dependencies = AppComponent.class, modules = {ActivityModule.class, MainActivityModule.class})
public interface MainActivityComponent extends MainFragment.Pluser{
void injectActivity(MainActivity mainActivity);
A provideA();
List<String> provideDiInjectionHistory();
Log4Me provideLog4Me();
}
Some example I've seen extends in the ActivityComponent the AppComponent the only reason I've found for doing that is that this way you avoid declaring again the methods that serve the previously provided objects. I don't like that because there is no functional relation and is something that can confuse the readers... Instead of extending AppComponent I extend... YES! A fragment inner interface!! The explanation is quite simple, after having a look at the fragment and his component...
MainFragment keeps the same Injection paradigm than BaseInjectionActivity but using BaseInjectionFragment instead
MainFragment.class MainFragment keeps the same Injection paradigm than BaseInjectionActivity but using BaseInjectionFragment instead
public class MainFragment extends BaseFragment<MainFragmentComponent> {
......
@Override protected void initDIComponent() {
fragmentComponent = (getParentComponent(MainActivityComponent.class)).plus(
new FragmentModule(this), new MainFragmentModule(this));
fragmentComponent.injectFragment(this);
}
public interface Pluser{
MainFragmentComponent plus(FragmentModule secondFragmentModule, MainFragmentModule baseFragmentModule);
}
}
MainFragmentComponent.class
@PerFragment
@Subcomponent( modules = {FragmentModule.class, MainFragmentModule.class})
public interface MainFragmentComponent{
void injectFragment(MainFragment mainFragment);
}
Yes, the reason is that I implement fragment components as subcomponents. I considered this implementation because relation between activity and fragment usually is close. And the hosting activity component is not declaring the "plus" methods of his fragments instead of this is extending the interfaces declared on those fragments. Using this technique the ActivityComponent does not have to manage so many different types and method that are not his duty. Those methods are declared inside the inner interfaces of each fragment. Above we can see the example, the declaration of the method and the call is responsibility of the same class and Activity only has to delegate specifying that implements some fragment interfaces.
Getting back to MainActivityComponent... the reason to declare again A, Log4Me and DiInjectionHistory is that there is another Component-Component relationship. Activity - Section.
The meaning of section is quite simple: sometimes a business operation requires a flow that can go forward and backwards over several fragments inside an Activity, moreover Activity contains several flows like this one per each big use case of the application. Each flow has to share information with the fragments that take part in the operation, but the Activity should not know anything about the operation, is not his responsibility. Sections try to be an interesting solution to this problem. Sections are one point between the Activity and the fragments that allow providing there all the tools that several fragments need and share singletons objects, @PerSection scoped properties better said, those properties are used along all the section steps/fragments. The appearance of this component is quite similar to ActivityComponents, because it works using the same paradigm. SectionComponent depends on MainActivityComponent.
@PerSection
@Component( dependencies = MainActivityComponent.class, modules = TestSectionModule.class)
public interface SectionComponentMain extends
FirstFragment.Pluser,
SecondFragment.Pluser{
}
Each fragment declares its own plus method.
MainActivity is the hosting activity so it manages the when is necessary to create a section component. For this app, FirstFragment and SecondFragment are inside a section. Look carefully at MainActivityCode:
public class MainActivity extends BaseInjectionActivity<MainActivityComponent> implements
View.OnClickListener {
private Button buttonClearFragments;
private Button buttonAddFirstFragment;
private Button buttonAddSecondFragment;
.....
private Object sectionComponent;
.....
@Override protected void initDI() {
activityComponent = DaggerMainActivityComponent.builder().appComponent(getAppComponent())
.activityModule(new ActivityModule(this))
.mainActivityModule(new MainActivityModule(this)).build();
activityComponent.injectActivity(this);
}
private void initSectionComponent() {
sectionComponent = DaggerSectionComponentMain.builder().mainActivityComponent(activityComponent).build();
}
private void clearSectionComponent(){
sectionComponent = null;
}
@Override
public Object getActivityComponent() {
if (sectionComponent!=null){
return sectionComponent;
}else{
return activityComponent;
}
}
@Override public void onClick(View v) {
switch (v.getId()){
case R.id.buttonAddFirstFragment:
initSectionComponent();
addFragment(new FirstFragment());
break;
case R.id.buttonAddSecondFragment:
addFragment(new SecondFragment());
break;
case R.id.buttonClearFragments:
clearSectionComponent();
clearFragmentStack();
break;
default:
break;
}
}
}
Section is initialized for FirstFragment and cleaned when stack pops all the elements. getActivityComponent returns the section if it is initialized because Fragments ask for it with the parametrized method getParentComponent(SectionComponentMain.class) that specifies the component which the Fragment belongs to.
Let's look inside FirstFragmentComponent.class
@PerFragment
@Subcomponent( modules = {FragmentModule.class, FirstFragmentModule.class})
public interface FirstFragmentComponent {
void injectFragment(FirstFragment firstFragment);
}
Like activities, in the initialization FragmetModule.class is for providing common instances for all fragments and FirstFragmentModule.class is about specific matters.
To keep in mind
Injected Class D, has not any provision method, instead the injection is resolved using its @inject annotated constructor.
@Inject annotated constructor cannot obviously provide injections base on interfaces. Moreover, this provisions are not able to specify any Scope, then all the injected fields will have a new instance every time.
TestSectionModule provides two objects. The first one, SectionProperty, has the @PerSection annotation that means that this object will be the same for the whole section so FirstFragment and SecondFragment will share the same object. On the other hand NotScopedProperty will not be dealt as a unique instance because is not an annotated with any scope.
E is provided exactly in the same way in FirstFragmentModule and SecondFragmentModule, using @PerFragment annotation. The result will be have an instance per fragment of E, absolutely independent.
When calling DaggerXXXXComponent.builder().build() as mentioned before the modules with no args constructor are automatically called. For subcomponents happens something similar, having:
@Subcomponent( modules = {FragmentModule.class, FirstFragmentModule.class})
Update:
Instead of having AppComponent that serves all the provided objects in the included modules, could be a good practice to create an interface per module that is a candidate to be served in app component and then serve the object there, later the only thing we have to do is extend this interface in our app component, activity component or whatever. Using this technique we achieve 2 interesting things, direct relation between the served objects in the interface and his corresponding module and we get the declared objects in the interface as a bunch of portable elements than can be easily added in any component. Have a look into the commit "Separate app Component sever objects to an interface, 3cb035485d154cb4d40c4b721ba841f5e0479e00" there you have an easy example about this update.
Output:
me.martinez.sergio.daggertwobasearchitecture.activities.MainActivity, //MainActivity Injected properties
me.martinez.sergio.daggertwobasearchitecture.utils.Log4MeImpl@41f70a90,
me.martinez.sergio.daggertwobasearchitecture.test.A@41f70320,
me.martinez.sergio.daggertwobasearchitecture.test.B@41f8a8c8,
me.martinez.sergio.daggertwobasearchitecture.fragments.MainFragment, //MainFragment Injected properties
me.martinez.sergio.daggertwobasearchitecture.test.B@41f8a8c8,
me.martinez.sergio.daggertwobasearchitecture.test.A@41f70320,
me.martinez.sergio.daggertwobasearchitecture.test.C@41fb6468,
me.martinez.sergio.daggertwobasearchitecture.fragments.sections.testsection.firststep.FirstFragment, //FirstFragment Injected properties
me.martinez.sergio.daggertwobasearchitecture.fragments.sections.testsection.firststep.presentation.FirstFragmentPresenter@41fdce58,
me.martinez.sergio.daggertwobasearchitecture.test.NotScopedProperty@41fdd538,
me.martinez.sergio.daggertwobasearchitecture.test.SectionProperty@41fdd1c8,
me.martinez.sergio.daggertwobasearchitecture.test.A@41f70320,
me.martinez.sergio.daggertwobasearchitecture.test.E@41fdc6d8,
me.martinez.sergio.daggertwobasearchitecture.test.D@41fdca00,
me.martinez.sergio.daggertwobasearchitecture.fragments.sections.testsection.secondstep.SecondFragment, //SecondFragment Injected properties
me.martinez.sergio.daggertwobasearchitecture.fragments.sections.testsection.secondstep.presentation.SecondFragmentPresenter@41fe2690,
me.martinez.sergio.daggertwobasearchitecture.test.NotScopedProperty@41fe26a0,
me.martinez.sergio.daggertwobasearchitecture.test.SectionProperty@41fdd1c8,
me.martinez.sergio.daggertwobasearchitecture.utils.Log4MeImpl@41f70a90,
me.martinez.sergio.daggertwobasearchitecture.test.E@41fe26b0,
me.martinez.sergio.daggertwobasearchitecture.test.D@41fe26c0
A is an App Singleton. B is a PerActivity scoped shared instance. SectionProperty is a PerSection scoped shared instance. All this fields are underlined. Otherwise, the red fields are not scoped and every time the injection returns a new instance. The rest of field are not being shared out of his injected class.
Summarizing
After all this information, that is more than I expected to write and maybe more than you expected to read... XD. I wonder if you liked both posts about Dagger 2. Please leave your comments all feedback is well received, try to criticize this post, improve it, copy it, whatever but don't forget to share your experiences.
Github Project: https://github.com/fSergio101/DaggerTwoBaseArchitecture