Difference between scope in modules and components - android

What is the difference between the #Singleton annotation on Dagger2 #Component annotated classes, and #Provides annotated methods in modules?
If I have one module in which all methods are annotated with a #Singleton annotation, and a component with the same annotation which includes that module, what is the purpose of this?
#Singleton
#Component(...)
public interface AppComponent {
// ...
}
And
#Provides #Singleton Context provideContext() { return context; }

Annotating the #Provides method (or the class with an #Inject constructor) tells Dagger to implement the actual scoping functionality whereas annotating the component (which is necessary) doesn't have any functionality, but tells Dagger "I allow this component to contain bindings of this scope". Note that you can still have unscoped bindings in a scoped component, but not the other way around.

It's well within the use of a Java annotation to provide documentation to reader, which is what you're probably seeing in your first example. It's useful there so the reader can know the intended use of the class/interface without having to know the mechanism by which its instance is created or managed.

Related

Un-scoped binding in modules vs Constructor injected binding in Dagger

I have an unscoped binding inside the appModule and the same class as constructor injected using the Singleton scope. when I add a declaration inside the appComponent for Foo, the generated code picks up the module binding without any DoubleCheck i.e unscoped binding over the constructor injected Singleton binding why is it so?
#Module
public class AppModule {
#Provides
public Foo provideFoo() {
return new Foo();
}
}
#Component(module = AppModule.class)
#Singleton
public interface AppComponent {
Foo getFoo();
}
#Singleton
class Foo #Inject constructor(){
//..
}
There is some hierarchy in how dependencies are provided, I haven't found documentation on this yet, but I ran into the same thing as well. If I recall it correctly from my testing, the hierarchy of where Dagger tries to get the dependency from is the following:
Bound instances (via component builder methods; i.e. #BindsInstance)
Module provision
Constructor injected classes
Somewhere in this list provisioned dependencies should probably also be included (provisioned from a parent component your component has a dependency on), but I dont know where it would be then in the hierarchy.
Usually there is no need to provide the same dependency in multiple ways, why would you both want to provide the dependency via a Module and via constructor injection? If there are multiple dependencies, but they live in a different scope, try using different scopes instead of just the singleton one.
I can think of one reason where you want to use this mechanic, and thats when you normally want a certain dependency to be provided via either constructor or module injection, but specifically for a test build, you want to overwrite that dependency with a different dependency by changing how the component is composed in a test build (i.e. then changing the DaggerComponent so it requires a #BindsInstance method to overwrite the default dependency with a test dependency).

Dagger hilt: Difference between annotating a class #Singleton and a provides function #Singleton

My question is pretty simple and straightforward: What is the difference between the two annotations / examples:
Example one
#Singleton
class MySingletonClass() {}
#Module
#InstallIn(FragmentComponent::class)
abstract class MyFragmentModule {
#Provides
fun provideMySingletonClass() = MySingletonClass()
}
Eaxmple two
class MySingletonClass() {}
#Module
#InstallIn(FragmentComponent::class)
abstract class MyFragmentModule {
#Singleton
#Provides
fun provideMySingletonClass() = MySingletonClass()
}
The only difference I know is, that the second example gives me the following error:
error: [Dagger/IncompatiblyScopedBindings] FragmentC scoped with #dagger.hilt.android.scopes.FragmentScoped may not reference bindings with different scopes:
Does that mean, that the #Singleton annotation in example one is simply ignored?
In Example One, your #Singleton annotation is ignored, but only because you are calling the constructor yourself in your #Provides method. Because Dagger doesn't interact with your MySingletonClass constructor, it cannot read or use the annotation.
If your #Singleton class MySingletonClass had an #Inject constructor—even an empty one—then Dagger would be able to interact with it directly as long as you also delete the #Provides fun that would override the constructor detection. Once you've done that, the behavior of #Singleton would be the same in either syntax.
Regarding the error message "error: [Dagger/IncompatiblyScopedBindings] XXX scoped with #YYY may not reference bindings with different scopes": #Andrew The real problem here is that in Example Two you're trying to declare a #Singleton binding in a Module that you install in your FragmentComponent. #Singleton bindings can only happen in a #Singleton component, which in Hilt is SingletonComponent. I don't remember for sure, but I think your Example One (with the edits I described) would work with singleton behavior and without an error, because Dagger would automatically select the appropriate component in the hierarchy to install your MySingletonClass.

How to pass arguments to Hilt module?

I started to migrate Dagger application to Hilt, first I'm converting AppComponent to Hilt auto-generated ApplicationComponent. Therefore I've added #InstallIn(ApplicationComponent::class) annotation to each module related to this component.
Now I get the following error:
error: [Hilt] All modules must be static and use static provision
methods or have a visible, no-arg constructor.
It points to this module:
#InstallIn(ApplicationComponent::class)
#Module
class AccountModule(private val versionName: String) {
#Provides
#Singleton
fun provideComparableVersion(): ComparableVersion {
return ComparableVersion(versionName)
}
}
Previously in Dagger, it was possible to pass arguments in the constructor. Looks like Hilt doesn't allow this.
How can I pass arguments to Hilt module?
#InstallIn(ApplicationComponent::class)
#Module
class AccountModule {
#Provides
#Singleton
fun provideComparableVersion(application: Application): ComparableVersion {
return ComparableVersion((application as MyApplication).versionName)
}
}
If you don't want to see MyApplication, then you can use an interface.
Unfortunately for now Dagger Hilt is design using monolithic component, where there's only one Application Component and one Activity Component auto generated by it. Refer to https://dagger.dev/hilt/monolithic.html
Hence the modules for it must be static and use static provision methods or have a visible, no-arg constructor.
If you put an argument to the module, it will error out stating
[Hilt] All modules must be static and use static provision methods or have a visible, no-arg constructor.
From my understanding, you'll trying to get the BuildInfo version number, perhaps the easiest way is to use the provided BuildInfo.VERSION_NAME as below.
#InstallIn(ApplicationComponent::class)
#Module
class AccountModule() {
#Provides
#Singleton
fun provideComparableVersion(): ComparableVersion {
return ComparableVersion(BuildInfo.VERSION_NAME)
}
}
If you like to set it yourselves instead of relying on BuildInfo.VERSION_NAME, you can define static const variable that exist differently across flavour.

What does it basically mean with scope annotations in two components (one depends on the other)

In my Android project, I have two project modules, an main module and a core module.
In main module, I have a dagger component, MainComponent:
// it has dependency on CoreComponent
#Component(modules = [MyModule::class], dependencies = [CoreComponent::class])
#FeatureScope
interface MainComponent {
fun inject(mainActivity: MainActivity)
}
As you can see above, MainComponent has a dependency on CoreComponent. It also has a custom scope annotation #FeatureScope.
In core module I have another dagger component called CoreComponent:
#Component(modules = [CoreModule::class])
#Singleton
interface CoreComponent {
fun getExpensiveObject(): ExpensiveObject
}
#Module
class CoreModule {
#Provides
#Singleton
fun provideExpObj(): ExpensiveObject = ExpensiveObject()
}
The CoreComponent is annotated by Dagger defined #Singleton scope.
I build the Main component in onCreate() of Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
//build main component along with core component
mainComponent = DaggerMainComponent
.builder()
.myModule(MyModule())
.coreComponent(DaggerCoreComponent.builder().build())
.build()
}
}
CoreComponent & its providers are annotated by #Singleton, while MainComponent & its providers are annotated by custom annotation #FeatureScope.
Question one: From lifetime perspective, does the code mean the lifetime of objects in MainComponent is shorter than that in CoreComponent due to the scope annotations (#Singleton in CoreComponent and #FeatureScope in MainComponent)?
Question two: Since the components are built in Application class onCreate() which is the entry point of app at runtime, am I right that even though components in two project modules are annotated by different scope annotation, their objects basically have the same lifetime as the whole app's at runtime?
(I ask those questions because my understanding is that the Dagger defined #Singleton scope has the longest lifetime, but I get confused by that with my project)
Yes, the fact that CoreComponent is annotated with #Singleton and the component instance is created in the Application means that there will be a single ExpensiveObject created in the lifetime of the application.
Concerning the custom annotation (#FeatureScope)
The component implementation ensures that there is only one provision of each scoped binding per instance of the component.
ref
But since, the MainComponent is created only once per application, this custom annotation is effectively the same as the Singleton annotation.
If you want a feature-scoped object then you should remove it from the MainComponent and have this annotation only on a sub-component. Read the dagger tutorial, and in particular the step 13.
Annotating both WithdrawalLimiter and UserCommandsRouter with
#PerSession indicates to Dagger that a single WithdrawalLimiter should
be created for every instance of UserCommandsRouter.
#PerSession
final class WithdrawalLimiter { ... }
#PerSession
#Subcomponent(...)
interface UserCommandsRouter { ... }

Dagger 2 Custom Scope for each Fragment (or Activity etc...)

I've looked at a couple different articles which seem to suggest two different ways of doing custom scoping in Dagger 2:
MVP Presenters that Survive Configuration Changes Part-2 (Github repo):
Uses unique custom scopes for each fragment, e.g. #Hello1Scope and #Hello2Scope for Hello1Fragment and Hello2Fragment respectively
Tasting Dagger 2 on Android:
Uses a single custom scope for all fragments, e.g. #PerFragment.
From what I understand, it seems that, as in method 2, it should be okay to have a single scope defined that can be used for all fragments (i.e., #PerFragment). In fact (please correct me if I'm wrong), it seems like the name of the custom scope is irrelevant, and it's only where the subcomponent is created (i.e. in Application, Activity, or Fragment) that matters.
Is there any use case for defining a unique scope for each fragment such as in case 1?
After reading the answer by #vaughandroid, and What determines the lifecycle of a component (object graph) in Dagger 2? I think I understand custom scopes well enough to answer my own question.
First, here are a couple rules when dealing with components, modules, and scoping annotations in dagger2.
A Component must have a (single) scope annotation (e.g. #Singleton or #CustomScope).
A Module does not have a scope annotation.
A Module Method may have a (single) scope that matches its Component or no scope, where:
Scoped: means a single instance is created for each instance of the component.
Unscoped: mean a new instance is created with each inject() or provider call
NOTE: Dagger2 reserves #Singleton for the root Component (and it's modules) only. Subcomponents must use a custom scope, but the functionality of that scope is exactly the same as #Singleton.
Now, to answer the question: I would say create a new named scope for each conceptually different scope. For example, create a #PerActivity, #PerFragment, or #PerView annotation that indicates where the component should be instantiated, and thus indicating its lifetime.
Note: this is a compromise between two extremes. Consider the case of a root component and n subcomponents you will need:
at least 2 annotations (#Singleton and #SubSingleton), and
at most n+1 annotations (#Singleton, #SubSingleton1, ... #SubSingletonN).
Example:
Application:
/** AppComponent.java **/
#Singleton
#Component( modules = AppModule.class )
public interface AppComponent{
void inject(MainActivity mainActivity);
}
/** AppModule.java **/
#Module
public class AppModule{
private App app;
public AppModule(App app){
this.app = app;
}
// For singleton objects, annotate with same scope as component, i.e. #Singleton
#Provides #Singleton public App provideApp() { return app; }
#Provides #Singleton public EventBus provideBus() { return EventBus.getDefault(); }
}
Fragment:
/** Fragment1Component.java **/
#PerFragment
#Component( modules = {Fragment1Module.class}, dependencies = {AppComponent.class} )
public interface Fragment1Component {
void inject(Fragment1 fragment1);
}
/** Fragment1Module.java **/
#Module
public class Fragment1Module {
// For singleton objects, annotate with same scope as component, i.e. #PerFragment
#Provides #PerFragment public Fragment1Presenter providePresenter(){
return new Fragment1Presenter();
}
}
/** PerFragment.java **/
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerFragment {}
Your understanding is correct. The named scopes allow you to communicate intention, but they all work the same way.
For scoped provider methods, each Component instance will create 1 instance of the provided object.
For unscoped provider methods, each Component instance will create a new instance of the provided object whenever it needs to inject it.
The lifetime of the Component instance is important though. 2 different instances of the same component will provide different object instances, even scoped ones.
Scope names should indicate the lifetime of the provided object (which matches that of the Component instance) so #PerFragment makes much more sense to me.
From a quick look at the "MVP Presenters..." tutorial, it's not clear to me exactly what the author's intention is with having separate scopes. Since the names are just throwaway ones, I wouldn't read too much into it.

Categories

Resources