Dagger Hilt error injecting ActivityContext - android

I'm injecting with Dagger-Hilt a class with a dependency on #ActivityContext in a ViewModel, this module is installed in ActivityComponent and scoped to activity and it's throwing me an error whenever I try to compile. For your information I have other modules with ActivityRetainedComponent and SingletonComponent injecting #ApplicationContext.
Now I'm trying to figure out what does this error mean.
error: [Dagger/MissingBinding] com.rober.fileshortcut_whereismyfile.utils.PermissionsUtils cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract static class SingletonC implements App_GeneratedInjector,
^
com.rober.fileshortcut_whereismyfile.utils.PermissionsUtils is injected at
com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel(�, permissionsUtils, �)
com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel is injected at
com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.rober.fileshortcut_whereismyfile.App_HiltComponents.SingletonC ? com.rober.fileshortcut_whereismyfile.App_HiltComponents.ActivityRetainedC ? com.rober.fileshortcut_whereismyfile.App_HiltComponents.ViewModelC]
Here's the code (I don't think there's anything wrong here)
#Module
#InstallIn(ActivityComponent::class)
object PermissionModule {
#ExperimentalCoroutinesApi
#Provides
#ActivityScoped
fun providePermissionsHelper(
#ActivityContext context: Context,
) = PermissionsUtils(context)
}
#HiltViewModel
#ExperimentalCoroutinesApi
class FileViewModel #Inject constructor(
private val class1: Class1,
private val class2: Class2,
private val class3: Class3,
private val permissionsUtils: PermissionsUtils //Here's the one I'm injecting
)
My guesses: It's telling me that I can't inject in ActivityComponent because FileViewModel is injected in ActivityRetainedComponent/SingletonComponent/ViewModelComponent, because other dependencies provided to viewmodel are installed in this component?
Question: What is really going here? What am I missing and is there any solution for using ActivityComponent? I really need the #ActivityContext for that class!
Edit: This works when I change to #InstallIn(ActivityRetainedComponent::class) and the context to #ApplicationContext context: Context, note that it works with SingletonComponent/ViewModelComponent/ActivityRetainedComponent, which makes sense. But still I don't know how to make it work the #ActivityComponent
Edit 2: I've come to the conclusion that is not possible since you are installing in ViewModel and the components that work with ViewModel are Singleton/ActivityRetained/ViewModel, so there's no way to inject ActivityContext in a ViewModel since it requires the component #ActivityComponent and this is 1 level below of ViewModelComponent.
Source: https://dagger.dev/hilt/components#fn:1

so there's no way to inject ActivityContext in a ViewModel since it requires the component #ActivityComponent and this is 1 level below of ViewModelComponent.
You got it.
Think of Dagger/Hilt components (and scopes) in terms of lifecycle. In Android, a ViewModel has longer lifecycle than its containing Activity or Fragment. That's the whole point of the class, it's just the name that's unfortunate. It may help to think of those as RetainedInstance or AnObjectThatSurvivesActivityConfigurationChanges. Yeah, these aren't as catchy as ViewModel.
If you injected a short-lived object (the Activity) into a long-lived one (the ViewModel), the latter would keep a reference to the former. That's a memory leak - the Activity and everything it contains (e.g. views, bitmaps) stays in memory even though it's no longer used. A couple of those and you risk an OutOfMemoryError.
However, the other way around - i.e. injecting a long-lived object into a short-lived one - is safe. That's why you got it working with Singleton/ActivityRetained/ViewModel components.
Hilt's component hierarchy makes it harder for you to create memory leaks. And all of that happens at compile-time, which gives you faster feedback as opposed to runtime-based solutions. Much better than your users finding out by crashing!

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

Why should I use viewmodelproviders for viewmodels?

Why should I use viewmodelproviders for viewmodels?
Why I just can't add custom singleton annotation to my viewmodel, and then inject this viewmodel to fragment class?
Like so:
#MainScope
class MainViewModel #Inject constructor(): ViewModel()
And then:
open class BaseFragment<T: ViewModel>: DaggerFragment() {
#Inject
protected lateinit var viewModel: T
Both cases are independent of screen rotation.
Is there any drawbacks of singleton annotation case?
I see only advantages, with this approach I don't need to copy/paste tons of code.
Why should I use viewmodelproviders for viewmodels?
To get viewModel.onCleared() callback called properly at the right time by the ComponentActivity.
(and to ensure it's created only once for the given ViewModelStoreOwner).
Why I just can't add custom singleton annotation to my viewmodel, and then inject this viewmodel to fragment class?
Because you won't get viewModel.onCleared() callback called properly at the right time by the ComponentActivity.
Is there any drawbacks of singleton annotation case? I see only advantages,
That you don't get viewModel.onCleared().
Also if you have a singleton variant, then the ViewModel won't die along with its enclosing finishing Activity, and stay alive even on back navigation (which is probably not intended).
with this approach I don't need to copy/paste tons of code.
You're using Kotlin. Use extension functions.

Dagger and mvp - should presenter use dagger for injection

I'm starting to think in mvp, dagger should not be used in the presenter. The usual way to construct dagger is using a global component and have subcomponents for scoping the graph. This global component will take an applicationContext as a parameter on creating the appmodule.java class usually. Having the application context given makes life easier.
That's all fine but if I use a module from the global component or even a subcomponent, the context should be passed in. So that means if I inject the presenter with dagger it will be tied to the applicationContext. THIS MAKES IT DIFFICULT TO TEST as junit. Android code should not be in presenter.
So I'm asking is the best practice to just use dagger in activities fragments broadcast receivers and services only? Speaking in terms of mvp architecture that is. The other solution could be to design another dagger component but not a subcomponent that is not related to appcomponent or an applicationContext. What is the common practice?
UPDATE:
LETS look at a real example:
usually we'd create a appComponent that's close in application override like this:
public class MyApplication extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = initDagger(this);
}
public AppComponent getAppComponent() {
return appComponent;
}
protected AppComponent initDagger(PomeloApplication application) {
return DaggerAppComponent.builder()
.appModule(new AppModule(application))
.build();
}}
and then for example in the presenter we'd do this in the constructor:
public WelcomePresenter(Context context) {
super(context);
presenterComponent = ((MyApplication) context).getAppComponent();
presenterComponent.inject(this);
}
so because I wanted the application Context for dagger providers I ended up forcing callers to depend on a context Object. Basically, you cant get the AppComponent modules until you get a context so you can cast it as "MyApplication" and inject from the component? So i was thinking why is this dagger pattern making me force the presenter to have a context?
Even if we used a subComponent it would still depend on the AppComponent and then in our test case we'd have to deal with an application context being created to get dagger to inject.
So I think there should be a rule - in MVP prefer manual constructor injection over dagger injection
Because class shouldn’t have knowledge about how it is injected. Something being injected should not care about how it is injected.
dagger.android outlines the problem i am talking about as per the accepted answer.
I also agree with the statement "Not having android related code in your presenter is always a good idea, and dagger does not interfere with that."
First, what I want to draw attention that starting from dagger version 2.10 you can use dagger.android package which simplifies injection in the activities and fragments, so you don't need to use MyApplication casting across the app.
Second, why are you not injecting WelcomePresenter into activity or fragment but vice versa?
Dependency injection is useful for Android components (activities, etc.) primarily because these classes must have a no-arg constructor so the framework can construct instances of them. Any class that isn't required to have a no-arg constructor should simply receive all its dependencies in its constructor.
Edit: A side note about how you're getting a reference to the AppComponent.
Application is-a Context, and the Context returned by Context#getApplicationContext() is usually the Application instance. But this is not necessarily the case. The one place I know of where it may not be the case is in an emulator. So this cast can fail:
presenterComponent = ((MyApplication) context).getAppComponent();
Mutable static fields are evil in general, and even more so on Android. But IMO, the one place you can get away with it is in your Application class. There is only one instance of Application, and no other component will ever exist before Application#onCreate() is called. So it's acceptable to make your AppComponent a static field of MyApplication, initialize it in onCreate(), and provide a static getter, as long as you only call the getter from the lifecycle methods of other components.

Dagger: need help in understanding

I have some misunderstandings of the way that dagger works:
There are only two ways to satisfy dependency: whether the #Provide method returns the instance, or the class should have #Singleton annotation, is that right? Must the class constructor have #Inject annotation in the latter case?
As I see, the ObjectGraph generates all the injection stuff. And it's said, that its inject(T instance) should be called to inject fields. However, I can just annotate my field with #Inject and it goes (field's class is #Singletone). ObjectGraph is not needed to satisfy such a dependency, right?
What about injects{} in #Module, what specifically does it give? Plz, provide an example of benefit when you keep all the injectable classes list.
Yes, there are two ways: #Provide methods in modules and #Singleton classes with #Inject at constructor.
Must the class constructor have #Inject annotation in the latter case?
Yes, otherwise object wouldn't be created by Dagger.
I don't think #Singleton with field injection could work. Because #Singleton at class with constructor injection means Dagger is responsible to keep one instance of this class. And it can create this class using constructor injection if all dependencies are satisfied. However, #Singleton with field injection seems misuse to me, cause keeping single instance of this class is user's responsibility now. Dagger can't instantiate this object itself.
Are you sure this configuration compiles and runs? And if it is check #Inject fields, they should be null by my understanding.
injects={} in #Module returns set of classes passed to ObjectGraph.inject(T class) or ObjectGraph.get(Class<T> class). Referring documentation:
It is an error to call ObjectGraph.get(java.lang.Class) or ObjectGraph.inject(T) with a type that isn't listed in the injects set for any of the object graph's modules. Making such a call will trigger an IllegalArgumentException at runtime.
This set helps Dagger to perform static analysis to detects errors and unsatisfied dependencies.
You can find some examples in this thread.

No injectable members on android.app.Application. Do you want to add an injectable constructor? required by provideOkHttpClient

I am trying to merge U2020 and Dagger's example android-activity-graphs in the following repo.
However, I am getting compile time error:
Error:(32, 8) error: No injectable members on android.app.Application. Do you want to add an injectable constructor? required by provideOkHttpClient(android.app.Application) for org.kamol.nefete.ActivityModule
You haven't bound Application, you have only bound Context, but somewhere else in your graph, you have some type that depends on Application. Downstream dependencies which need Application will attempt to implicitly bind it because Dagger cannot know that an object which requires Application can be satisfied by #ForApplication Context. Since Application does not have an #Inject constructor nor #Inject fields, Dagger will fail.
You can fix your example by simply adding
#Provides #Singleton Application provideApplicationContext() {
return application;
}
in org.kamol.nefete.AndroidModule
As an aside, I recommend against binding Context as it is too abstract a supertype and too easy to confuse with Activity Contexts, even if you are binding with a #Qualifier annotation, such as #ForApplication Context. I would recommend instead binding Application and having things depend on the more concrete type.

Categories

Resources