Am I injecting context safely? Dagger, Android Studio warning - android

From what I understand reading other answers here and researching, injecting the Application Context into a field should be safe from memory leaks, whereas holding Activity Context in a field would cause a memory leak.
I am injecting Application context with Dagger like so:
AppModule:
#Singleton
#Provides
fun provideContext(application: Application): Context {
return application
}
ViewModel:
// Injected context provided by Dagger
#Inject
lateinit var mContext: Context
Android Studio still throws this warning on the injected context field:
This field leaks a context object
Is it actually leaking a context object, or is Android Studio just not able to determine that its the Application Context that gets injected and I should ignore the warning? Do I need to inject it as a weak reference? Thanks.

Technically you can't leak the Application context, because the application context is available as long as the Application is alive. (Kind of self explanatory).
Easiest way would be to ignore the warning, it shouldn't get you in trouble.
If you still want to fix the warning, you could inject a WeekReference of the context. (WeekReference). This would probably fix your issue but it would need a null check before each use of the context.
Also, if you are using the Android LiveCycle ViewModel, you should know that you could use the AndroidViewModel (instead of simple ViewModel) that will require a context instance to be passed in the constructor, and you can use that instead of the Application context.

Related

Dagger Hilt error injecting ActivityContext

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!

Why do we use Application Context in Constructors in Android?

While using Room Database i found that classes like Repository and ViewModel used Application Context as arguments in their Constructors.
I Just want to know the reason,why this is done?
And is it a Compulsion to use the application context?
public WordViewModel (Application application) {
super(application);
mRepository = new WordRepository(application);
mAllWords = mRepository.getAllWords();
}
WordRepository(Application application) {
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
mAllWords = mWordDao.getAllWords();
}
Curious to Know the reason behind the stuff
Why can't we use a Activity Context?
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
ViewModel objects are designed to outlive specific instantiations of views or LifecycleOwners. This design also means you can write tests to cover a ViewModel more easily as it doesn't know about view and Lifecycle objects.
Why can we use a Application Context?
If the ViewModel needs the Application context, for example to find a system service, it can extend the AndroidViewModel class and have a constructor that receives the Application in the constructor, since Application class extends Context.
See ViewModel
let me give you a lazy answer to this question. Application context lifecycle is tied to the lifecycle of the application whereas activity context is tied to the lifecycle of that activity. Make sure to use the right context to prevent memory leaks . Hope this helps

Android Architecture Components ViewModel Context

I'm studying google's architecture components to implement ViewModel and LiveData to my app, and the official documentation says that:
Note: Since the ViewModel outlives specific activity and fragment instantiations, it should never reference a View, or any class that may hold a reference to the activity context. If the ViewModel needs the Application context (for example, to find a system service), it can extend the AndroidViewModel class and have a constructor that receives the Application in the constructor (since Application class extends Context)
Following that, I ended up with a code like that:
public class ViewModelTest extends AndroidViewModel {
public ViewModelTest(Application application) {
super(application);
}
public void test(){
Prefs.getCurrentCode(getApplication());
}
And should I instantiante it normally on the activity?
val viewModel2 = ViewModelProviders.of(this).get(ViewModelTest::class.java)
viewModel2.test()
Isn't it bad? To use this application variable when need to access SharedPreferences or anything that need a context?
And if it is, should I avoid using it on the ViewModel and use it only on the view? Specially if I want to update a UI component with a value that needs a context. I kinda don't know how to approach this issue, and I'm open for any suggestions.
Thanks in advance
AndroidViewModel class is provided as part of the android.arch.lifecycle package which is part of Android's architecture components. It itself calls for the Application Context passed into the constructor. The Application Context lives across the Activity lifecycle.
An Application context in a ViewModel is okay because the Application context is tied to the whole Application lifecycle as opposed to an Activity context, which is tied to the Activity lifecycle.
ViewModel documentation specifically is referring to not use the Activity Context, but the Application Context if fine.

Should Context be injected with Dagger?

I know it is possible to inject Context with Dagger. We can see examples here and here.
On the other end, there are numerous posts about not placing context on a static variable to avoid leaks.
Android Studio (lint) also warms about this:
Do not place Android context classes in static fields; this is a
memory leak (and also breaks Instant Run)
I understand that by injecting a Context with Dagger, we are placing it on a singleton class, so context is somehow static. Doesn't this go against the lint warning?
Injecting the context seems to create cleaner code, since you don't have to pass it to several classes (that don't need it) so that they can further pass it to other classes that need it for some reason (getting a resource for instance).
I am just concerned that this may cause some undesired leak or breaks lint in some way.
You should never store/reference activity context (an activity is a context) for longer than the lifetime of the activity otherwise, as you rightly say, your app will leak memory. Application context has the lifetime​ of the app on the other hand so is safe to store/reference in singletons. Access application context via context.getApplicationContext().
If you are aware of Android lifecycles and are careful to distinguish the Application Context and the Context of Activities and Services then there is no fault injecting the Context using Dagger 2.
If you are worried about the possibility of a memory leak you can use assertions to prevent injection of the wrong Context:
public class MyActivityHelper {
private final Context context;
#Inject
public MyActivityHelper (Context context) {
if (context instanceof Application) {
throw new IllegalArgumentExecption("MyActivityHelper requires an Activity context");
}
}
}
Alternatively you could use Dagger 2 Qualifiers to distinguish the two so you don't accidentally inject an app Context where an Activity Context is required. Then your constructor would look something like this:
#Inject
public class MyActivityHelper (#Named("activity") Context context) {
Note also, as per David's comment, a Dagger 2 #Singelton is not necessarily a static reference.

AndroidViewModel vs ViewModel

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

Categories

Resources