I have complex multi-tier architecture in my Android project.
Currently i want to use the following structure of the DI components and modules:
[Data
It's not a bug, it's a feature
The problems that would emerge from allowing mixing scopes in the manner you have suggested are similar to the problems of multiple inheritance in OO languages.
Consider the following component structure (the diamond problem):
A
/ \
B C
\ /
D
D
sub-component of both B
and C
. Since siblings B
and C
are independent of each other they are free to expose an arbitrary dependency Foo
to sub-components. Assume that Foo
is required for the module set in component D
. Which Foo
should D
now use? The one from B
or the one from C
?
The situation is even more complex if you allow scopes. A scope in Dagger 2 is for marking lifecycle. In other words, it says "I will maintain a reference to these bindings at this point". Hence, your @Singleton
scoped bindings must be grouped in a Component for which you maintain a reference at the level of your Application subclass. It does nothing, for instance, to initialise a @AppScoped
Component as a local variable within a method or as a field of an Activity - it will be discarded with the method stack or destroyed when the Activity is destroyed.
Returning to the above example, assume that B
and C
have different scopes. The Foo
bound in B
is @BScoped
. So it should only live as long as B
. But it is possible for D
to get the @CScoped Foo
from C
. So we can no longer ascertain that B
is exposing a @BScoped Foo
to its subcomponent - we have allowed the possibility of Foo
to escape its scope. This is in addition to the processing problems presented by not restricting ourselves to directed acyclic graphs. As you are probably aware, many problems in computer science become easier to solve once we disallow cycles.
The fact that you get the error message for your set up only when you add scope annotations and it disappears when you remove them is not indicative that there is a bug in Dagger 2 or something lacking. It simply means that without scope annotations you only have the illusion of a Component hierarchy since all Components are effectively equal. Again, even if you think you have somehow made a diamond through combination of non-scoped and scoped components, it will not truly be a diamond because there is no real hierarchy in the non-scoped Components.
Your proposed architecture is well-intentioned in that you are trying to achieve separation of layers. However, Dagger 2 components are for grouping lifecycle, not layers. To illustrate, most Android apps will have app-scoped dependencies, such as a GSON
, a SharedPreferences
etc. These dependencies that live and die together and last for the whole lifecycle of your app are normally bound in the module set for a @Singleton
component. You could also call it @PerApp
or @AppScoped
- it really doesn't matter as long as you retain a reference to the component in your sub-class of Application
.
Underneath this in your directed acyclic graph (the DAG in DAGger) you can put what you want. Some people make a @UserScope
which lasts just as long as the app has a signed-in user. Others skip this step and move straight to @PerActivity
or @ActivityScoped
components. The module sets for the @PerActivity
components should bind members that have the lifecycle of a single activity e.g., the Activity's Context.
At this point it's better to group by functionality like in the Google Android Architecture Blueprint. Although your components at this level will inject dependencies from different layers (the model layer and the presentation layer) these can be separated by judicious use of modules. Additionally, you can group the members will be in the same Java package which means you can use access modifiers to fulfil Effective Java Item 13: Minimise the accessibility of classes and members.