How to create custom scope and share same instances using Dagger Android - 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.

Related

Activity scoped regular class that acts as a ViewModel

In the Using Dagger in your Android app codelab tutorial they use an activity scoped regular class that acts as a ViewModel like so
#ActivityScope
class RegistrationViewModel #Inject constructor(val userManager: UserManager) {
...
}
That makes ViewModel injection by Dagger very simple but won't we loose anyting if we don't derive from the architecture components ViewModel class?
In general, the code labs are related to some topic and they try to explain only this topic. Here it is Dagger, not Architecture Components. Yes, you can lose some functionality, but if they still can make their point - it does not matter.
Also if they make the app work with only plain java objects it means that they don't need the extra functionality from the ViewModel, they wrote less code so even better.
I also want to point out that the explanation that "you are losing ViewModel.onCleared" is "the smallest problem". What is the "main feature" of the VM is that you can share the same instance through the life cycle of the same Activity/Fragment or that you can share it between different Activity/Fragment.
And onCleared is something that should be used with caution because it means in some situations that you are trying to clear a reference to a thing you should not be holding in the first place.

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.

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

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?

Understanding Android Architecture Components example GithubBrowserSample: ViewModelModule, ViewModel parameters

One of the most up to date samples covering Android Architecture Components is GithubBrowserSample provided by Google. I reviewed the code and a few questions arose:
I have noticed that ViewModelModule is included in AppModule. It means that all the viewmodels are added to the DI graph. Why that is done in that way instead of having separate Module for each Activity/Fragment that would provide only needed ViewModel for specific Activity/Fragment?
In this specific example, where viewmodels are instantiated using GithubViewModelFactory is there any way to pass a parameter to the specific ViewModel? Or the better solution would be to create a setter in ViewModel and set needed param via setter?
[...] It means that all the viewmodels are added to the DI graph. Why that is done in that way instead of having separate Module for each Activity/Fragment [...]?
They are added to the DI graph, but they are not yet created. Instead they end up in a map of providers, as seen in the ViewModelFacory.
#Inject
public GithubViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) { }
So we now have a GithubViewModelFactory that has a list of providers and can create any ViewModel that was bound. Fragments and Activities can now just inject the factory and retrieve their ViewModel.
#Inject
ViewModelProvider.Factory viewModelFactory;
// ...later...
repoViewModel = ViewModelProviders.of(this, viewModelFactory).get(RepoViewModel.class);
As to the why...alternatively you could create a ViewModelProvider.Factory for every Activity / Fragment and register the implementation in every Module. This would be a lot of duplicated boilerplate code, though.
In this specific example, where viewmodels are instantiated using GithubViewModelFactory is there any way to pass a parameter to the specific ViewModel? Or the better solution would be to create a setter in ViewModel and set needed param via setter?
It seems like all the ViewModels only depend on #Singleton objects—which is necessary, since they all get provided from the AppComponent. This means that there is no way to pass in "parameters" other than other #Singleton dependencies.
So, as you suggested, you'd either have to move the factory down into the Activity / Fragment component so that you can provide lower-scoped dependencies, or use a setter method.

is it ok to inject content view using dagger2

I'm using data binding in my android project, also i'm using dagger 2 for DI.
basically for setting content view with data binding i need to do something like this :
LayoutClass layoutClass = DataBindingUtil.setContentView(Activity, Layout);
I'm providing that layoutClass in dagger module and injecting it to my activity. the question is, is this a good practice ?
Technically you're defining a circle-reference with this. You're just not warned, because setting up the graph requires you to be pro-active about this.
The dependencies would look like activity -> layout -> activity while you provide the module with an activity explicitly. Additionally you're modifying the activity with DataBindingUtil.setContentView() and therefore provide a dependency to the activity, which actually is a property of the activity itself.
So, never provide any UI with Dagger. Especially not to an activity.

Categories

Resources