Dagger: Multi-layer / modules architecture with dagger2: designing scopes and components

Created on 17 Mar 2017  路  11Comments  路  Source: google/dagger

I'm using dagger 2 in a multi-layered, multi-library android SDK.

I'm developing many different modules on different layer (data, business logic, presentation etc...) and using dagger to glue them together.

My most important requirement is that each module of the architecture should be usable stand-alone (with its dependent components) and that a developer should decide to build on top on any layer he wants (ex. rewrite all the presentation layer using everything below).

I've created for each library a Dagger component with a custom scope but sometimes I've a component that depends on 2-3 other components and Dagger complain that only 1 dependency component should be scoped. (Es. Domain layer using a service component to obtain data from a company service and a sensor component to obtain device sensor data / connectivity or whatever).

I can't get rid of scopes because I need those component to be scoped / singleton.

My current workaround is to pass the dependency component to the module constructor, but this look like a workaround and I would like to know what's the right way to approach this kind of requirement with Dagger 2.

I've read about subcomponents but looks like those can't be used stand-alone unless you write a component for each.

Can you point me in the right direction?
Thanks.

All 11 comments

IMO, components are an application's responsibility; libraries should only provide modules. It also looks like your scopes aren't tied to objects' lifecycle / lifetime, which is a smell of misuse, that components aren't the solution to your problem.

Someone with more experience with Dagger please confirm or refute.

@tbroyer thank you for your feedback. I do not see however how I can only expose modules when a module depends on a dependency declared by another library.

As far as I understood reading the Dagger doc components are (or should be) like black box exposing stuff. While modules "just" provides a mixture of classes.

What If 2 modules provides the same class but are supposed to use different instances? What If I depend on something that should be managed by another module/component.

I don't think module alone is a solution to this issue. But I also think the current scoping I'm using is not optimal.

I agree with @tbroyer here. Each library can provide their own scope and the application which includes those libraries can choose how to apply those scopes. With scope aliasing, you could combine these scopes if necessary:

@LibraryOneScope
@LibraryTwoScope
@Component
interface C { ... }

If 2 modules provide the same class, they should be using @Qualifiers. You don't often want to provide a type you don't own/didn't write in the same library without a @Qualifier

@ronshapiro how am I supposed to provide a qualifier for something if I do not know which modules will be used togheter?

Say I have two libraries providing an instance of OkHttp:

@Provides
public OkHttp providesOkHttp() {...}

Maybe that's obvious there I need a qualifier. Should I qualify everything?

I wasn't aware I could give 2 scopes to a component and that those become aliases.

I'm still not convinced that providing only modules is the right thing to do. The compoment is the "public part" of the library while the module is the actual implementation and I guess it could also become an interface + default implementation.

Also: if I do not expose a component for each library I force who use the library to write the boilerplate code to put it all together, which should not be the case in my opinion.

This discussion seems a level removed from the actual issue and it makes it hard to actually comment on it. Sometimes you may want to provide a component, and other times modules. It's probably best to ask this on stack overflow. this isn't really an _issue_ with dagger.

There is a quite similar (multiple Android modules with scopes) question on StackOverflow that is solved by exposing modules instead of components.

I eventually solved exactly this problem by using a mixture of constructor injection (wherever possible) and AndroidInjection for Activities in my library modules. I was able to inject every dependency needed by my library modules, without having to create a library component. I had initially tried to solve the problem by adding a component for my libraries, but rapidly got lost in a mess of cyclic dependencies, scoping issues, etc. AndroidInjection really saved the day.

I have exactly the same issue, where we have libraries depending on other libraries. We are using dagger to bind objects and exposing component as a public part of the library.

The issue we have is binding whole graph at the same time and scope conflicts.
Workaround I found is to create interface for the component and reference this interface as your dependency in other libraries. And use actual component as an implementation of this interface.

This solves the problem with scoping. Each library can live it is own life.
You also no longer need to have custom scopes inside libraries (until you actually need to use subcomponents), since, imo, this should be client responsibility to decide how long to keep graph alive and how to bind components.

Here is I created an example:
https://gist.github.com/vovkab/c97b9530a5470336975dde37d8db2ea7

We ended up doing the same thing. We concluded dagger simply isn't designed to build libraries. It is great for a single application or a single library as long as you do not expose dagger to app using your library.

So we use dagger in each library at each layer of our architecture but layers and users of our libraries do not interact with each other through dagger.

I still think this is an huge limitation for a dependency injection library.

I have been following this thread for some time with interest having stumbled into the scope collision just after Dagger2 reach fruition and released as stable.

I too, in my personal Android client (where it is Dagger2, end-to-end) have resorted to passing around components and have felt pressure (from work colleagues mainly!) whereby my vision of the app's multi-module, multi-layer was back to front. The counter view to mine is that all of the scope collision woes disappear if instead of creating a tree (imagine a Christmas tree upside down) you create a circle with a nucleus! Alas, that nucleus is something like ApplicationComponent. What I see regularly is an abuse of Dagger's scoping and of course rampant misunderstanding of what adding @Singleton actually does!

From my perspective, much of the utility of Dagger2 scopes is not necessary under Android considering that the main object lifecycles are already catered with recommended, alternative solutions. The most obvious of these is process singletons. I read a while back on G+ Gregory Kick explaining that if you truly want a once per process object (the scenario was similar there as here whereby scope collision meant Dagger wasn't 'assisting'), use the Effective Java enum pattern. As in just write a Singleton and move on with life. Things got more interesting with the intermediate 'use-case' scoping like "@LoggedIn". I've not encountered this need in my time building for Android since more often than not the use-case coincides with an Activity and I can simply use a ViewModel (or more specifically, the ViewModelFactory) should I need an object's memory ref to survive a configuration change. Finally, it seems many mobile engineers are obsessed with scopes owing to IMO a misguided, unproven take on 'performance' or 'efficiency'. Codebase after codebase is strewn with @Singleton with huge Modules performing all manner of injection tasks. Little do they realise that allocating all these objects, upfront is the very thing slowing their apps down.

Dagger is my favourite tool. It saves me so much time and I defend it to the hilts. No, Dagger isn't slow, the 40 SDKs you initialise at app start up with Dagger doing the leg work is slow. And that's never going to go away. @ronshapiro, if you think there is legs to what I'm saying, then I'll do you that blog post on this topic of using Dagger in a "tree formation" (if that makes sense!), rather than the "nucleus" version you see in the basic 1-2 app module case.

Was this page helpful?
0 / 5 - 0 ratings