How to pass android Activity Context to Koin Module? - android

I checked few post but haven't found any query related to my problem.
My use case:
I have Dependency in my Activity which are dependent on the Activities Context and should not be allowed to be given Application Context as that can lead to Memory leak's.
single { (activity: Activity) ->
MyDependency(activity)
}
if use above solution in my case then it can solve the case for one Dependency but then my activity code will be to responsible to send param for each dependency which need's activity's context (beating the use case of dependency injection)
Is it possible to make some specific koin module's dependent on android activity context while other's on application context?
if i use androidContext(this) in my Application Class while starting koin then all module's requiring the Context will get() application Context.

Related

Am I injecting context safely? Dagger, Android Studio warning

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.

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.

Order of dependency injection when using scopes

I'm currently trying to figure out Dagger 2. I am trying to set up 4 scopes: App, User, Activity, Fragment. User and Activity components are Subcomponents of App. Fragment is a Component with Activity as its dependency.
Say my UserSettingsActivity needs a Toolbar (provided by ActivityModule), and a UserProfile (provided by UserModule). I won't get a UserProfile until I ask for it from the database, whereas the Toolbar can be provided right away. So the order of injection that takes place is into ActivityComponent first, then into UserComponent. I have 2 #Inject fields, one for Toolbar and one for UserProfile in the activity. I was hoping that dagger will know that the dependencies are coming from different modules, but it seems to complain that UserProfile can't be provided when injected into ActivityComponent. Obviously it can't be provided by ActivityModule, but why is it not making a connection that UserProfile is provided by UserModule?
To my best knowledge, Dagger-2 doesn't support "partial injections".
Therefore, when you call myComponent.inject(this), Dagger-2 throws an error if myComponent can't provide all #Inject annotated members of this.
I see two ways to work around this limitation:
Remove #Inject annotation from UserProfile, expose UserProfile via public method in UserComponent and inject it manually when UserComponent is ready to be used. Something analogous to this: userProfile = userComponent.getUserProfile()
Don't make UserComponent dependent on data fetching. UserComponent could be used to inject Toolbar and some UserProfileProvider at the same time, and you will fetch UserProfile from UserProfileProvider when it is available.
I personally think that second approach is the better choice. DI libraries should be used in order to satisfy objects' dependencies at construction time. In Android we can't construct Activity or Fragment ourselves, therefore we perform DI in onCreate(), onAttach(), onCreateView(), etc., but it does not mean that we should be using DI libraries in order to assist in controlling the flow of applications.
Subcomponents work's similar to inheritance(extends), in your case User component and Activity component extending App component but there is no relation between User component and Activity component so when you request User dependency in Activity it will fail.
Subcomponent can't provide any dependency to other Subcomponent.
Instead, you can make Activity component as a subcomponent of User component. This will also give you the flexibility to switch user.

android.content.res.Resources$NotFoundException. Module, aar library

I have library module with string resource.
string.xml
<string name="lib_ver">1.0</string>
and method:
public static String getLibVersion(Context context){
return context.getResources().getString(R.string.lib_ver);
}
In my app application i include my module like aar library.
Everything work properly except 1 thing.
if i try to get lib version with lib method in activity class:
getLibVersion(getApplicationContext()) i get error:
android.content.res.Resources$NotFoundException: String resource ID
#0x7f02105b
But if i do in activity class, without call to library method:
getApplicationContext().getResources().getString(R.string.lib_ver)
There is no errors. Where is a problem? Thx.
just need to update gradle to 2.1.2
classpath 'com.android.tools.build:gradle:2.1.2'
getApplicationContext: Return the context of the single, global Application object of the current process. This generally should only be used if you need a Context whose lifecycle is separate from the current context, that is tied to the lifetime of the process rather than the current component. From android
That might be the reason that your ApplicationContext is throwing error! :)
More info:
Context and ApplicationContext are both instances of Context, but the application instance is tied to the lifecycle of the application, while the Activity instance is tied to the lifecycle of an Activity. Thus, they have access to different information about the application environment.
If you read the docs at getApplicationContext it notes that you should only use this if you need a context whose lifecycle is separate from the current context. This doesn't apply in either of your examples.
The Activity context presumably has some information about the current activity that is necessary to complete those calls. If you show the exact error message, might be able to point to what exactly it needs.
But in general, use the activity context unless you have a good reason not to.

Get application context from a secondary module

I am working on a project that has one main module(here I have the activities and the controllers..) and some secondary modules where I have some calendar and other implementations.
In the main module I have a Application singleton class where I store the application context and I can get the app context statically from everywhere within my main module.
The question is how can I make another application class in the secondary module? Currently I am using circular dependencies between the main module and the module where I want the app context, and I don't like too much to use this approach.
#David Wasser wrote:
Why can't the code in the secondary modules call MainApp.getInstance() to get the application context? Obviously the secondary module is dependent on the main module so I don't see how this is a circular dependency.
If not, then pass the singleton application context from the main module to the secondary modules (either as a parameter in method calls or as a parameter in the constructor of the component in the secondary module. Then you won't have code in the secondary modules calling MainApp.getInstance(). In any case you cannot have another application class as there is only one application class.

Categories

Resources