How to declare Dagger activities and fragments for Android feature modules? - android

How to declare activities and fragments for non-base feature modules?
As we had only a base feature module and a single feature module, we could declare the App class in the feature module and had our graph be loaded there. This meant being able to use ContributesAndroidInjector and the standard dagger approach for android.
Now, adding more feature modules, we can't do the same. The application class must stay in the base feature module, which means it can't declare, for example, activities belonging to features.
My thoughts:
Since AndroidInjection.inject(this) will look for the activity injector in the app class, we can't use it. In other words, no DaggerAppCompatActivity.
So the idea is that activities belonging to feature modules should, instead, create their own component and inject themselves.
Still, it should be possible for fragments to use the #ContributesAndroidInjector thing. Right? The AndroidInjection class will get the injector from the parent activity, so if we fix our activities, they will expose the correct injector so that fragment code can be left as is.
For this reason, the feature activities must implement HasFragmentInjector and have a #Inject annotated DispatchingAndroidInjector for fragments.
But two things don't work here.
Feature fragments and activities still need some #Singleton annotated objects from the base feature component graph, so our SpecialFeatureComponent must somehow be linked to the BaseFeatureComponent.
It seems that the only way of doing so is by using the dependencies parameter:
#Component(
dependencies = [BaseFeatureComponent::class],
modules = [SpecialFeatureModule::class] // #contributes fragment
)
interface SpecialFeatureComponent
The SpecialActivity creates this component, passes the BaseFeatureComponent to its builder, and injects itself.
However, compilation fails due to MissingBinding errors. Some of the objects in the special feature module need #Provide, #Singleton annotated objects from the base feature component, and dagger doesn't seem to find them properly. (these objects are not in BaseFeatureComponent, but rather in its attached modules)
How to fix this?
I have read that exposing them directly in BaseFeatureComponent, rather than in its dependency modules, should fix the issue, but it's not something we would like to do, as there are lots of them and it would be yet another list to be maintained.
As is, the unscoped SpecialFeatureComponent depends on the #Singleton scoped BaseFeatureComponent. This is not possible, so we have to add a ActivityScope annotation.
#ActivityScope
#Component(
dependencies = [BaseFeatureComponent::class],
modules = [SpecialFeatureModule::class] // #contributes fragment
)
interface SpecialFeatureComponent
Now we are told that the subcomponents generated by #ContributesAndroidInjector for fragments, unscoped, may not reference scoped bindings. So we add a #FragmentScope annotation there.
#Module
abstract class SpecialFeatureModule {
#FragmentScope
#ContributesAndroidInjector
internal abstract fun specialFragment(): SpecialFragment
}
Now we are told that these subcomponents may not reference bindings with a different scope! Which are #Singleton objects provided by the base feature graph.
How to fix this?

Related

Dagger2 scope, instance per component

I'm looking for a quick confirmation about Dagger 2 scopes in Android.
In many resources online you will find that #ActivityScope and #FragmentScope are added to components that provide bindings for activities and fragments.
I would like to have some confirmation that this implies that there will be 1 instance for all activities / all fragments respectively.
That is, if, say, two activities use the same component for receiving dependencies from the same component annotated with scope 'Activity', both activities will receive the same instance (like singleton annotation would work).
So in that case having #ActivityScope and #FragmentScope annotations would only be useful to segregate between dependency lifetimes between activities versus fragments.
So if I would need a dependency object for which I need a separate instance in two activities, I should scope them explicitly (e.g. #LoginActivityScope).
Could you confirm that this assumption is correct?
Edit:
Reading the docs about subcomponents, it confuses me a bit:
No subcomponent may be associated with the same scope as any ancestor
component, although two subcomponents that are not mutually reachable
can be associated with the same scope because there is no ambiguity
about where to store the scoped objects. (The two subcomponents
effectively have different scope instances even if they use the same
scope annotation.)
This would seem to assume that if you have multiple components using the same annotation, it does create a separate instance when the same scope annotation is used for different components.
I find it a bit unclear as to what a scope instance refers to. This actually refers to the binding?
Does this only apply to subcomponents?
Some clarification about scope vs dependency instances (bindings) would be very helpful.
A scoped component will create a scoped object the first time it is used, then it will hold on to it. If you create the same component a second time it will also create the scoped object the first time it gets used. Components are just objects, they don't hold any global (static) state, so if you recreate the component, you recreate everything along with it.
val component = DaggerScopedComponent.create()
component.getScopedObject() === component.getScopedObject() // always the same object!
// never the same object! two different components, albeit same scope
DaggerScopedComponent.create().getScopedObject() != DaggerScopedComponent.create().getScopedObject()
Dagger generates code, so I would invite you to create a simple example and have a look at the code. e.g. the sample above should be very easy to read
#Singleton class Foo #Inject constructor()
#Singleton #Component interface ScopedComponent {
fun getScopedObject() : Foo
}
If you have a scoped component that lives longer than its subscopes then you have to keep a reference to this component and reuse it. The usual practice is to hold a reference to the component in the object whose lifecycle it shares (Application, Activity, Fragment) if needed.
Let's say we add a subcomponent to the example above
#Singleton class Foo #Inject constructor()
#Singleton #Component interface ScopedComponent {
fun getScopedObject() : Foo
fun subComponent() : SubComponent
}
#Other #Subcomponent interface SubComponent {
fun getScopedObject() : Foo
}
#Scope
#MustBeDocumented
annotation class Other
As long as we use the same #Singleton component we will always get the same #Singleton scoped objects.
// Subcomponents will have the same object as the parent component
component.subComponent().getScopedObject() === component.getScopedObject()
// as well as different Subcomponents
component.subComponent().getScopedObject() === component.subComponent().getScopedObject()
Now on to your questions...
I would like to have some confirmation that this implies that there will be 1 instance for all activities / all fragments respectively.
That is, if, say, two activities use the same component for receiving dependencies from the same component annotated with scope 'Activity', both activities will receive the same instance (like singleton annotation would work).
As shown above, any scoped object provided from the same scoped component will be the same no matter which subcomponent. If you create two #ActivityScope MyActivityComponent then everything scoped #ActivityScoped will be created once per component.
If you want objects to be shared between your Activities' components you have to use a higher scope and keep the reference to the created component.
So in that case having #ActivityScope and #FragmentScope annotations would only be useful to segregate between dependency lifetimes between activities versus fragments.
No, because you can have a #ActivityScope FooActivityComponent and a ActivityScope BarActivityComponent and they would never share a #ActivityScope class FooBar object, which will be created once for every #ActivityScope scoped component.
So if I would need a dependency object for which I need a separate instance in two activities, I should scope them explicitly (e.g. #LoginActivityScope).
#ActivityScope FooActivityComponent and #ActivityScope LoginActivityComponent will never share any #ActivityScope scoped objects. You can use the same scope here. You can also create a different scope if you like to do so, but it would make no difference here.
This would seem to assume that if you have multiple components using the same annotation, it does create a separate instance when the same scope annotation is used for different components.
Yep
I find it a bit unclear as to what a scope instance refers to. This actually refers to the binding? Does this only apply to subcomponents?
You can't have a hierarchy of components like Singleton > ActivityScope > ActivityScope since those duplicated scopes would make it impossible to know whether a #ActivityScope scoped object was part of the first or second one.
You can have two different components of the same scope, both subcomponents of the same parent (they can't "reach" each other), and any #ActivityScope scoped object would be part of the latter #ActivityScope scoped component. You'd have one scoped object per component (as shown in the example above) and you could have two component instances or more.
Singleton > ActivityScope FooComponent
Singleton > ActivityScope BarComponent
I recommend you forget about Android for a bit and just play around with Dagger and the generated code, like with the code shown on top. This is IMHO the quickest way to figure out how things work, once the "magic" is gone and you see that it's just a POJO with a few variables in it.

Creating all modules for DI in the Application class for Android

One of the things I've noticed a lot of developers do is to create a class that inherits from Application and then create a component through dependency injection that includes virtually all the modules that make up their app. This is done in the onCreate method. I find this rather strange. Why would you want to inject every module into an Application class and make it globally available. After all, most of the modules like presenters are bound to a single activity and will never be used for any other activity. So why would you not just create a component in the activity and only include those modules you need, which in the case of an activity would be a presenter class.
I'm not sure I agree with the premise: Most applications create a component in Application#onCreate, but I believe that most applications also have separate components that contain per-activity, per-fragment, or per-service bindings, and those components/modules only exist and are only classloaded when you use the specific activity/fragment/service in question.
Scope and lifecycle
Dagger manages object lifecycle ("scope") through separate components, each of which can have its own set of modules. You annotate your component with one or more scope annotations, and then any bindings you annotate with the same scope (or any classes with that scope annotation and #Inject-annotated constructors) will be created exactly once and stored within the component. This is in contrast to Dagger's default behavior, which is to call a #Provides method or create a new object instance for each call to a component method or each #Inject-annotated field. You are in control of when you create a component instance, so you can control the semantics of your scope: If you were to create a scope annotation called #PerActivity, and you create a new component instance for each Activity instance Android creates, then you can be sure that any bindings marked #PerActivity will return the same instance across the lifetime of that Activity. Likewise, you might create a #UserScope where every user gets a separate component instance. The only standardized scope in JSR-330 is #Singleton, which should apply to the entire application.
However, what if you want to mix scopes, such as having a #PerActivity StatusBarPresenter depend on a #Singleton LoginService? Dagger requires you to keep those in two separate components, such that StatusBarPresenter might be defined in an #PerActivity ActivityComponent and LoginService might be defined in a #Singleton ApplicationComponent. You would need to establish a relationship between this ActivityComponent and ApplicationComponent, which can be done through either components with dependencies or subcomponents.
Components with dependencies
Components with dependencies receive separate code generation, and list their dependencies in the dependencies attribute on the #Component annotation. At that point, you'll need to specify an instance of that Component on their
#Singleton #Component(modules = {FooModule.class, BarModule.class})
interface ApplicationComponent {
Foo foo();
// Bar also exists, but is not listed. Let's say Foo uses it internally.
}
#PerActivity #Component(
modules = {BazModule.class},
dependencies = {ApplicationComponent.class})
interface ActivityComponent {
Baz baz();
}
ActivityComponent activityComponent =
DaggerActivityComponent.builder()
.applicationComponent(yourExistingApplicationComponent)
.build();
ActivityComponent receives its own code generation step, and can compile in parallel with ApplicationComponent; however, ActivityComponent can only access dependency Foo and not Bar. This is because ActivityComponent has ApplicationComponent listed as a dependency, and and ApplicationComponent doesn't list Bar. So Baz, defined on the ActivityComponent, can automatically inject Foo but cannot inject Bar. In fact, if Foo stopped consuming Bar, ApplicationComponent may not even generate the code to create Bar at all.
Subcomponents
In contrast, subcomponents are generated as a part of their parent component, such that the parent component acts as a factory.
#Singleton #Component(modules = {FooModule.class, BarModule.class})
interface ApplicationComponent {
Foo foo();
// This is a subcomponent builder method, which can also return a
// #Subcomponent.Builder. More modern code uses the "subcomponents" attribute
// on the #Module annotation.
ActivityComponent createActivityComponent();
}
#PerActivity #Subcomponent(
modules = {BazModule.class},
dependencies = {ApplicationComponent.class})
interface ActivityComponent {
Baz baz();
}
ActivityComponent activityComponent =
yourExistingApplicationComponent.createActivityComponent();
// or, from somewhere that ApplicationComponent injects:
#Inject Provider<ActivityComponent> activityComponentProvider;
ActivityComponent activityComponent = activityComponentProvider.get();
For subcomponents, the implementation of ActivityComponent is generated at the same time as ApplicationComponent, which also means that ApplicationComponent can evaluate ActivityComponent's needs when it is generating code. Consequently, the code for creating Bar instances will be included in ApplicationComponent if either ApplicationComponent or ActivityComponent consumes it. However, this build step may become slow, because Dagger will need to analyze your entire application's dependency graph.
Application#onCreate
All of this gets back to Application#onCreate, and to what you're seeing:
If you have application-scoped singletons, you probably need to get a hold of them from the application (though technically you could also use a static field).
If you have bindings that are used across your application, even if they're unscoped, you may want to install them in ApplicationComponent just so you don't have to repeat the same binding in each component.
If you're using subcomponents, then all of your code generation for the whole app is generated in one step at the Application level even though the code is generated as separate classes loaded at separate times. Because the application component acts as a factory, the extra classes are hidden, because your one and only reference to a generated class name may be for the ApplicationComponent instance ("DaggerApplicationComponent").
This may result in a smoother developer experience, because you don't need to worry about listing a dependency on the ApplicationComponent interface if you want to access it from an ActivityComponent.
This is also nice for Android, because in the subcomponent case Dagger has more information about which bindings are required, so it can sometimes produce more compact code than components-with-dependencies.
If you're using dagger.android and #ContributesAndroidInjector, you are using subcomponents with some syntactic sugar on top. Note that #ContributesAndroidInjector can be annotated with scope annotations, and can take a list of modules which will be passed along to the subcomponent that it generates. Your call to AndroidInjection.inject(this) will create one of those subcomponent instances, classloading the subcomponent and its modules as needed.
So even though you may have very specific components with different lifecycles, it may look like all of your Dagger configuration happens in ApplicationComponent and Application#onCreate and nowhere else.

No injector factory bound with #ContributesAndroidInjector

Please don't mark this duplicate, I've read all the other answers about this issue. I'm not asking what the issue means, I'm asking why this particular code produces this error.
I'm trying to make an Activity component/injector that uses SessionComponent as its parent:
AppComponent:
#Singleton
#Component(modules = [AppModule::class, AndroidSupportInjectionModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(ltiApp: LTIApp): Builder
fun build(): AppComponent
}
SessionComponent:
#SessionScope
#Component(
dependencies = [AppComponent::class],
modules = [SessionModule::class, CommentaryModule::class, EducationCenterModule::class])
interface SessionComponent {
EducationCenterModule
#dagger.Module
abstract class EducationCenterModule {
#EducationScope
#ContributesAndroidInjector()
abstract fun educationCenterActivity(): EducationCenterActivity
}
How come I get an error for injector factory even though I have #ContributesAndroidInjector inside a Module?
Caused by: java.lang.IllegalArgumentException: No injector factory
bound for
Class
If possible, move your #ContributesAndroidInjector into your AppModule, which will likely involve some refactoring between AppComponent and SessionComponent.
dagger.android injects Activity instances by calling getApplication() on the Activity, casting that to a HasActivityInjector, and calling activityInjector().inject(activity) on it (code). In turn, apps that use DaggerApplication (or the code on the dagger.android how-to page) will inject a DispatchingAndroidInjector<Activity>, which injects a Map<Class, AndroidInjector.Builder> that is built using multibindings. Though it's possible to inject into this map directly, you may also use #ContributesAndroidInjector (as you have done here) as a shortcut that produces the multibinding and subcomponent.
Though you have #ContributesAndroidInjector bound inside a #Module, you have two top-level components: AppComponent and SessionComponent. dagger.android is not prepared for this: Your Application likely uses AppComponent for its injection, and because you haven't installed EducationCenterModule into AppComponent, the multibound map will not contain the binding that your #ContributesAndroidInjector method installs.
This probably requires some refactoring, but for important reasons: Through intents, back stacks, and activity management, Android reserves the right to recreate your Activity instance whenever it wants to. Though your Application subclass likely guarantees that an AppComponent will exist by then (by creating and storing that component within onCreate), there is no such guarantee that your SessionComponent will exist, nor any established way for an Android-created Activity instance to find the SessionComponent that it can use.
The most common way to solve this problem is to separate the Android lifecycle from your business logic, such that dagger.android manages your Android components on their own lifecycle, and those Android components create/destroy SessionComponent and other business logic classes as needed. This may also be important if you ever require Service, ContentProvider, or BroadcastReceiver classes, as those will definitely only have access to the application, and may restore or create sessions of their own. Finally, this also means that a Session will necessarily last longer than an Activity instance, which might mean that your Session will not be garbage collected until Android destroys your Activity, and may also mean that you have multiple concurrent SessionComponent instances.
Edit/elaboration: First and foremost you'll need to decide whether sessions outlive Activities, Activities outlive Sessions, or neither. I bet it's "neither", which is fine: at that point I'd write an injectable #Singleton SessionManager that goes inside AppComponent and manages the creation, recreation, and fetching of SessionComponent. I'd then try to divide it so most of the business logic is on the SessionComponent side, and by calling sessionManager.get().getBusinessObject() you can access it from SessionComponent. This also works well to keep you honest, since the business logic side might be easy to unit test using Robolectric or Java, while the Android side might require instrumentation tests in an emulator. And, of course, you can use #Provides methods in your AppComponent modules to pull out your SessionComponent, BusinessObject, or any other relevant instance out of the SessionManager side.
However, if you are 100% sure that you want SessionComponent to be the container for your Activity, and that you don't mind managing the session creation and deletion in your Application subclass. If that's the case, then rather than using DaggerApplication, you can write your Application subclass to implement HasActivityInjector (etc), and delegate the activityInjector() method to a class on a SessionComponent instance that you create. This means that AppComponent would no longer include AndroidSupportInjectionModule, because you no longer inject DispatchingAndroidInjector or its Map. However, this is an unusual structure with implications for your application, so you should consider your component structure carefully before proceeding and document it heavily if you choose the non-standard route.

How to create custom scope and share same instances using Dagger Android

So here are the things I know from the doc
Dagger Android under the hood is creating subcomponent for each Activity annotated with ContributesAndroidInjector
You can apply custom scope to the method where ContributesAndroidInjector is annotated to
If two sibling subcomponents have the same scope, they will still have different scope instances
If an Activity is in a subcomponent, it can have its own subcomponent which can contain Fragments. Those Fragments will share the scoped instances the Activity has.
Now my question is:
How to have one Activity be a subcomponent of another activity using Dagger Android?
I want to do this because I want to achieve things like #UserScope/#SessionScope.
From this I know that I can do it with just Dagger not Dagger Android. But with Dagger Android, you can only have the Application (which is the AndroidInjector) to inject Activity. You can not have an Activity used as a holder or host of the parent subcomponent to inject another Activity.
Am I understanding it correctly?
05/14/2018 Update:
I ended up getting rid of Dagger Android. So no more ContributesAndroidInjector, just pure Dagger. And to inject Activity/Fragment, I use the way that's recommended here. It will be something like this:
class MyActivity : AppCompatActivity() {
private val factory: ViewModelProvider.Factory = Injector.myCustomScope().factory()
}
And we are trying to make sure the factory is the only thing that Activity/Fragment needs.
So far it's been great.
How to have one Activity be a subcomponent of another activity using Dagger Android?
tl;dr You can't. Dagger Android follows a strict AppComponent > ActivityComponent > FragmentComponent scheme and there is no way to add custom scopes in-between.
I suggest you have a look at the Dagger Android source code, it's really not that much. It's basicalle a HashMap for each layer where you look up the component builder and build the subcomponent. A fragment looks at its parent Activity, an Activity looks at the Application. There is no feature where you can add custom components between layers.
What you can do is create your own variant of "Dagger Android" where you can implement your own interfaces and mix/match components as you need them. But that's quite a bit of extra work. I created a #PerScreen scope that survives configuration changes as a proof of concept if you are interested to see how you could do such a thing.
You can create a custom Scope called for example #PerScreen, also you will have #PerActvity scope. The difference between these scopes is that the #PerActivity scope will maintain shared dependencies between all activities like Context, Layout Inflater, etc. And all activity specific dependencies will be scoped as #PerScreen.
#PerApplication -> #PerActivity -> #PerScreen
This could structured like that.
I have explained scopes under the hood in my blog post, you can refer to it to get better understanding of this matter.

Dagger 2 singleton not working

We are creating a dagger 2 dependency graph
SessionComponent (Session scope) --dependson---> Appcomponent (Singleton scope) ---dependson---> UserMangerComponent (unscoped... suppose to be singleton)
However, when I inject userManager (a dependency provided by UserManagerComponent), It is not being maintained as singleton. Every injection is creating a new UserManager. Please help ...
I cannot set singleton scope to the dependency.
Your proposed structure is incompatible with how Dagger manages scope. Only one component in your app should ever be #Singleton because each binding within a component that is not exposed via a component interface might be #Singleton, but entirely encapsulated in the component implementation. Thus, each component would hold its own instance and you'd end up with 2 instances rather than one.
Either merge the two components into one #Singleton component, create a new scope for your user management, or implement the instance management for your user manager by hand.

Categories

Resources