Dagger Android and scope - android

I am following architecture-components-samples: GithubBrowserSample
I have a Viewmodel that has a dependency and is provided through constructor injection, Since App component is singleton dagger forces me to use Singleton scope only if I use any other scope for those components I get below error
com.example.AppComponent scoped with #Singleton may not reference bindings with different scopes:
I tried this as well but still same
#Binds
#IntoMap
#ViewModelKey(MyViewModel::class)
#PerActivity
abstract fun bindMyViewModel(myViewModel: MyViewModel): ViewModel

Related

Android HILT SingletonComponent vs the GoF Singleton instance design pattern

In an Android project, there is a facade implemented as the singleton. I thought it is an better idea converting it to DI with HILT SingletonComponent.
#Module
#InstallIn(SingletonComponent::class)
object MyManagerModule {
#Provides
fun provide(#ApplicationContext context: Context): MyManager {
return MyManager(context)
}
}
class MyManager #Inject constructor(#ApplicationContext private val ctx: Context){
//
}
From a few callers, I get the instance of the above MyManager using HILT field injection, i.e.,
#AndroidEntryPoint
class MyCallerFragment : Fragment() {
#Inject lateinit var myManager: MyManager
// ...
In the debugger, I observed the DI instances are actually NOT the same instance (assuming these fragments are in the same activity lifecycle). I think I must have misunderstand the Hilt DI :-( and would love to hear any explanation if you see my blindspots.
TL;DR
You need to use the annotation #Singleton. This will tell Hilt to use the same instance of MyManager throughout your app.
Scope of Bindings
According to the documentation:
By default, all bindings in Hilt are unscoped. This means that each time your app requests the binding, Hilt creates a new instance of the needed type.
and
However, Hilt also allows a binding to be scoped to a particular component. Hilt only creates a scoped binding once per instance of the component that the binding is scoped to, and all requests for that binding share the same instance.
The #Singleton annotation scopes your Hilt binding to the application component. (including all children, which are all components) So Hilt will inject the same instance of your object throughout your entire app.
There is an example in this guide by Google.
The #InstallIn annotation
The annotation #InstallIn() tells Hilt in which component MyManager objects will be injected.
In your case of #InstallIn(SingletonComponent::class), Hilt will make MyManager available for injection in the application component and all children of this component, which doesn't mean that Hilt will supply the same instance though. Since any default component is a child of the application component, MyManager is currently accessible for injection in any component. (according to the documentation)
I had kinda the same question, Scope vs Component in Hilt, and this is what I got:
When you annotate a class with #InstalIn, actually you are defining the lifetime of this dependencies' module. For example, if you use SingletonComponent all dependencies from this class(Module) are stay as long as the application is up.
And for Scope, when you provide a dependency in a module, each time that module is called, a new instance is created. this is why you observed in debugger generated instances are not the same. by using Scopes like #Singleton you are changing the way of instantiation of dagger-hilt on called dependencies.
so generally speaking, Component is for instance lifetime and Scope is for the way of dependency instantiation on each call.
don't miss this article:
https://medium.com/digigeek/hilt-components-and-scopes-in-android-b96546cb07df

How to scope dagger dependencies with fragments when using Fragment Factory?

Without using FragmentFactory, scoping dependencies within Fragment is straigth forward. Just create a Subcomponent for your fragment and just create the Fragment's Subcomponent in onAttach().
But when using FragmentFactory you no longer inject dependencies via the subcomponent but instead pass then in Fragment's constructor.
I was wondering if I could still declare a dependency which should only last within the fragment's lifecycle using Dagger. I currently could not think of a way to achieve this.
So instead of binding my dependencies to a certain scope, I just declare the dependency with any scope or just use #Reusable on them.
Also, since fragments are created through FragmentFactory, the created Fragments doesn't exist in the DI graph.
How can we properly scope dependencies to fragment and also be able to add the fragment in the DI graph when using FragmentFactory?
You can accomplish this by making a Subcomponent responsible for creating your Fragment. The Fragment should have the same scope as this Subcomponent.
#Subcomponent(modules = [/* ... */])
#FragmentScope
interface FooSubcomponent {
fun fooFragment(): FooFragment
#Subcomponent.Factory
interface Factory {
fun create(): FooSubcomponent
}
}
After taking care of any cyclic dependency issues, this Subcomponent acts the same as if you had explicitly created a subcomponent in onAttach() using #BindsInstance, except that you can (and must) now use constructor injection on FooFragment.
In order to provide FooFragment in your main component (or parent subcomponent), you will need to install this Subcomponent into a module.
#Module(subcomponents = [FooSubcomponent::class])
object MyModule {
#Provides
#IntoMap
#FragmentKey(FooFragment::class)
fun provideFooFragment(factory: FooSubcomponent.Factory): Fragment {
return factory.create().fooFragment()
}
}
Some caveats:
The scenario you describe (FooFragment depends on some class which depends on FooFragment) is the definition of a cyclic dependency. You will need to inject a Lazy or Provider at some point in this cycle, or Dagger will throw an error at compile time.
If you split this up into two steps, first providing FooFragment and then binding it into your map, the subcomponent will see the #Provides method from its parent component. This is likely to cause a StackOverflowError if you aren't careful.

Dagger 2 scope issue, define child fragment module. result: binding is not resolved for interface

I want to use Dagger to compose a module for AFragment with child fragments (X...Z)FragModule.
Starting out from the ActivityBindingModule, I define the an example activity's dependent modules
/* ActivityBindingModule.java */
#Module
public abstract class ActivityBindingModule{
#NonNull
#ActivityScoped
#ContributesAndroidInjector(modules = {
AFragModule.class,
BFragModule.class
// ... fragment modules
})
abstract MainActivity mainActivity();
}
In AFragModule, I define its child fragment dependent modules.
/* AFragModule.java */
#Module
public abstract class AFragModule{
#NonNull
#FragmentScoped
#ContributesAndroidInjector(modules = {
XFragModule.class,
YFragModule.class
// ... child fragment modules
})
abstract AFragment providesFragment();
}
In (X...Z)FragModule, I defined its dependent objects' provider methods.
/* XFragModule.java */
#Module
public abstract class XFragModule{
#FragmentScoped
#ContributesAndroidInjector
abstract XFragment providesFragment();
#ActivityScoped
#Binds
abstract XContract.Presenter providesPresenter(XPresenter presenter);
}
I want the (X...Z)FragModule modules to be scoped within the AFragModule. And (X...Z)FragModules' presenters alive within the same scope for inter-presenter communication.
The XPresenter implementation itself uses constructor injector, with #Singleton parameters (eg. datasources)
I get the following error with the above scopes:
Cause: binding is not resolved for XContract.Presenter: ProvisionBinding{contributionType=UNIQUE, key=XContract.Presenter, bindingElement=Optional[providesPresenter(XPresenter)], contributingModule=Optional[XFragModule], kind=DELEGATE, nullableType=Optional.empty, wrappedMapKeyAnnotation=Optional.empty, provisionDependencies=[DependencyRequest{kind=INSTANCE, key=XPresenter, requestElement=Optional[presenter], isNullable=false}], injectionSites=[], unresolved=Optional.empty, scope=Optional[#ActivityScoped]}
EDIT:
I want to check my understanding of scope as well:
I "think" I understand the following
#Singleton > #ActivityScope > #FragmentScope
Scopes can't depend on the same or smaller scope
ex. #ActivityScope can't depend on #ActivityScope or #FragmentScope
Scopes can depend on any bigger scope.
ex. #FragmentScope can depend on #Singleton, #ActivityScope marked methods.
In your specific case, it looks like Dagger can't find the binding for XPresenter; you have a statement that #Binds XContract.Presenter to XPresenter, but based on your casual mention of "The XPresenter implementation itself" it looks like you might be missing a statement like:
#Binds
abstract XPresenter providesXPresenter(XPresenterImpl presenterImpl);
Scopes can depend on the same scope: Items in #ActivityScope can depend on #ActivityScope but not #FragmentScope. If you try to depend on a #FragmentScope object from #ActivityScope (a "scope-widening injection"), Dagger will prevent it and describe the components where you can find that injection.
However, you also will likely run into the trouble that a #FragmentScope component cannot contain other #FragmentScope components, which is a problem given that #ContributesAndroidInjector necessarily creates a new subcomponent that takes the scope and modules listed on the method. You'll need to adjust your choice of scope annotations, such as creating and using #ParentFragmentScope and #ChildFragmentScope. This is also important because your #ChildFragmentScope Fragment XFragment can inject objects that share the lifecycle of XFragment's subcomponent instance, AFragment's subcomponent instance, MainActivity's subcomponent instance, or your root #Singleton component.
Of course, you'll probably want to name them according to your use case, like #FullScreenFragmentScope or #TabFragmentScope or #OptionalFlowFragmentScope; you can also choose the outer scope to keep #FragmentScope and the inner scope to be #SubFragmentScope or so forth, which might be particularly useful if you have reusable modules that already use #FragmentScope. The point is precisely that a reusable module that uses #FragmentScope is not going to be clear about whether it is tracking AFragment's lifecycle or XFragment's lifecycle, so you're going to need to be clearer about that.

Bidirectional dependency with Dagger 2

I'm experimenting with VIPER architecture in my Android app. I use Dagger 2.11 for DI.
Dependencies I have for each VIPER module are:
Presenter and Fragment are linked via ViewInput and ViewOutput interfaces.
Presenter and Interactor are linked via InteractorInput and InteractorOutput interfaces.
Some other dependencies, we are not interested in.
This is how my Dagger Module looks like:
Module(includes = [Module.Declarations::class])
class Module(private val viewInput: ViewInput) {
#Module
interface Declarations {
#Binds
#FragmentLevel
fun bindViewOutput(viewOutput: Presenter): ViewOutput
#Binds
#FragmentLevel
fun bindInteractorOutput(interactorOutput: Presenter): InteractorOutput
#Binds
#FragmentLevel
fun bindInteractorInput(interactorInput: Interactor): InteractorInput
}
#Provides
#FragmentLevel
fun provideViewInput() = viewInput
}
Now when I call inject in my Fragment the following happens:
Presenter is injected into Fragment via ViewOutput interface
Interactor is injected into Presenter via InteractorInput interface
Presenter is not injected into Interactor, because of Lazy type (to prevent cyclic dependencies).
After the first call of Lazy.get() Presenter is injected into Interactor via InteractorOutput interface.
The problem is that in the 1st and 4th steps different instances of Presenter are injected. How can I make dagger inject the same presenter into Fragment and Interactor?
Or maybe I need to fix the dependency cycle in the other way?
The problem is fixed after adding #FragmentLevel scope to Presenter.

Dagger 2.13: controlling scope of a constructor-injected class

I am using Dagger Android 2.13 and am in the process of setting up Activity-scoped dependencies.
I understand how to specify scope for dependencies declared inside a Module:
#Module
public class MyActivityModule {
#Provides
#PerActivity
MyActivityDataRepo provideMyActivityDataRepo() {
return MyActivityDataRepo(); // simplified for the sake of clarity
}
}
But how would I specify scope of a class added to dependencies graph via constructor injection such as below?
class MyActivityOtherDataRepo {
#Inject
MyActivityOtherDataRepo() {
}
}
Is there any way to make this class Activity Scoped for MyActivity?
Or will it be effectively Activity Scoped as soon as it's injected into MyActivity via member injection? And if so, is there a way to restrict scoping to MyActivity only? All I can think of to do so is to make MyActivityOtherDataRepo package private and place it in the same package as MyActivity.
You can scope an element by:
annotating the #Provides or #Binds annotated method with a scope
#Provides
#PerActivity
MyActivityDataRepo provideMyActivityDataRepo() { /*...*/}
or adding a scope annotation to the class itself with constructor injection
#PerActivity class MyActivityOtherDataRepo {
#Inject
MyActivityOtherDataRepo() { /*...*/}
}
It will be scoped by this scope, so any component within #PerActivity, as well as any subcomponents will be able to provide anything #PerActivity scoped.
The visibility of your class (public / package private) does not directly affect this scope, but of course you would not be able to import the class in other parts of your app.

Categories

Resources