In regards to #ApplicationContext and #ActivityContext in Hilt, I understand that they help to resolve ambiguity when a Context is being requested… but why not just request Application or Activity directly?
It depends on what you need, when you just need a context(call methods in the context class), then inject the context. If you really need an Activity, you can inject it to your class.
But when you inject some class, it means your class depends on it and they are coupled. That's a bad practise in software design. When you inject an activity into your class, that means your class only works with that activity. If you inject an context, your class can work with any subclasses that implements the Context class
Related
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!
I am using a library that needs the Application class (not the application context) that I am using to work
its builder is something like Library.Builder.setApplication(app).setParameters(...).build()
is there a way to use the currently created Application in Hilt? #ApplicationContext obviously does not work
I'm not 100% sure, but maybe you can cast Application Context to Application class. I had similar problem when I needed Activity instance in provides method. I cast Activity Context from Hilt annotations and it works.
Simply inject the Application class:
class MyClass #Inject constructor(
private val application: Application,
)
The project I'm working on has a number of utility classes that require activity context.
I don't want to have to declare a new #Provides method for each activity that uses the dependency. i.e. I don't want this:
#Provides
static Navigator providesNavigator(ActivityOne activity) {
returns new Navigator(activity);
}
// ...and in another module
#Provides
static Navigator providesNavigator(ActivityTwo activity) {
returns new Navigator(activity);
}
So instead I declare these utilities in a single ActivityUtilitiesModule and pass our BaseActivity which all other activities extend. Now i don't have to declare my Navigator dependency x number of times.
#Provides
static Navigator(BaseActivity activity) {
return new Navigator(activity);
}
However, Dagger does not know how to satisfy the dependency for BaseActivity. This means for every Activity i need to create a provides method that will satisfy the BaseActivity dependency with the specific Activity being used. e.g.:
#Provides
static BaseActivity providesBaseActivity(ActivityOne activity) {
return activity;
}
This is better - I only need to repeat this one provider per activity, rather than repeating a provider for every utility class per activity, but it still feels like an unwelcome additional step in Dagger set up, and another thing that makes the code harder to understand.
Is there a pattern which allows me to avoid having to supply this BaseActivity provider per activity?
Please use Constructor Injection. Having provide* methods that only call the constructor are only noise and code to maintain. Add #Inject to the class constructor and possible scopes onto the class.
#SomePossibleScope
class Navigator {
#Inject
Navigator(Activity activity) { }
}
If you do this, you probably don't need your ActivityUtilitiesModule at all.
If your class depends on an Activity or BaseActivity then you need to provide it. And yes, you will have to tell Dagger about it in some way.
If you were to use an abstract class or interface you should make use of #Binds instead.
#Binds BaseActivity bindsBaseActivity(ActivityOne activity);
Compared to #Provides Dagger might optimize this code further, reducing the number of method calls and object creations, as well as a few less lines of code.
I don't know why your Utils depend on the Activity, but if they would only need a Context then you could also just provide the application context to them without a need to bind or provide your actual Activity.
I personally just bind the current Activity to the types it implements using the syntax above. And if you're using Constructor Injection properly that's more often than not the only lines of code that you'll find in my modules, making them very readable.
With the introduction of the Android Architecture Components library, several new classes were introduced, including AndroidViewModel and ViewModel. However, I'm having trouble figuring out the difference between these two classes. The documentation succinctly describes AndroidViewModel as follows:
Application context aware ViewModel
I appreciate the brevity, but what exactly does this imply? When should we choose to use AndroidViewModel over ViewModel and vice-versa?
AndroidViewModel provides Application context
If you need to use context inside your Viewmodel you should use AndroidViewModel (AVM), because it contains the application context. To retrieve the context call getApplication(), otherwise use the regular ViewModel (VM).
AndroidViewModel has application context.
We all know having static context instance is evil as it can cause memory leaks!! However, having static Application instance is not as bad as you might think because there is only one Application instance in the running application.
Therefore, using and having Application instance in a specific class is not a problem in general. But, if an Application instance references them, it is a problem because of the reference cycle problem.
See Also about Application Instance
AndroidViewModel Problematic for unit tests
AVM provides application context which is problematic for unit testing. Unit tests should not deal with any of the Android lifecycle, such as context.
Finally I got something a simpler explanation, a bit......
...The AndroidViewModel class is a subclass of ViewModel and similar to them, they are designed to store and manage UI-related data are responsible to prepare & provide data for UI and automatically allow data to survive configuration change.
The only difference with AndroidViewModel is it comes with the application context, which is helpful if you require context to get a system service or have a similar requirement. the bold text makes it clearer to sense it.
AndroidViewModel is subclass of ViewModel. The Difference between them is we can pass Application Context which can be used whenever Application Context is required for example to instantiate Database in Repository.
AndroidViewModel is a Application context aware ViewModel.
AndroidViewModel:
public class PriceViewModel extends AndroidViewModel {
private PriceRepository priceRepository;
public PriceViewModel(#NonNull Application application) {
super(application);
priceRepository= new PriceRepository(application);
allPrices = priceRepository.getAllPrices();
}
ViewModel:
public class PriceViewModel extends ViewModel {
public PriceViewModel() {
super();
}
You Should use AndroidViewModel only when you require Application
Context.
You should never store a reference of activity or a view that references a activity in the ViewModel.Because ViewModel is designed to outlive a activity and it will cause Memory Leak.
Apart from the difference that AndroidViewModel gives you an application context whereas ViewModel does not. The important thing that you must understand is that Google itself recommends using ViewModel and not AndroidViewModel.
So, don't use AndroidViewModel unless it is really necessary.
See this: GOOGLE DOC
How can I create a singleton that gets injected with an application context. Just annotating with #Singleton and then using #Inject on the constructor ends up generating an UnscopedProvider class that will not compile. How can I make an ApplicationScoped provider... or is there another mechanism to accomplish this?
The solution I ended on here is to create an init(Application app) {} method on the Singleton, then inject the Singleton into my #Application annotated class and call the init method using the Application that's injected into that class. I think this is the only way to do this currently.