Dagger 2 injecting Android Application Context
Was not correctly building the Application component, needed to pass in the Application. This Dagger 2 example perfectly shows how to do this: https://github.com/google/dagger/tree/master/examples/android-simple/src/main/java/com/example/dagger/simple
Update:
Working link: https://github.com/yongjhih/dagger2-sample/tree/master/examples/android-simple/src/main/java/com/example/dagger/simple
Inject Application Context in Repository with Dagger 2
You can use a dependency which provides these assets to the repository. And this dependency can contain a reference to the context. So your repository can simply query this dependency to get the assets it requires.
Here's a gist of it:
AssetProvider:
class AssetProvider @Inject constructor(
private val context: Context
) {
fun getDescription() = context.assets.open("tab1/item1/description.txt")
}
Repository:
@Singleton
class Repository @Inject constructor(
private val assetProvider: AssetProvider
) {
fun loadList(pageIndex: Int): List<String> {
val input = assetProvider.getDescription()
...
}
}
I like having repositories that have minimal dependency on Android specific stuff. So the repository logic is agnostic to the platform it runs on. This also helps in unit tests where you don't have to inject context to test your repository.
How to provide context with Dagger 2?
You are all good at using @Component.Builder but it can be further optimized.
Following are the changes:
Step 1 : Use @Binds
in Context Module to provide Context
package com.mamak.geobaza.di
import android.content.Context
import dagger.Module
import dagger.Provides
@Module
abstract class ContextModule { // to allow abstract method make module abstract
//@Binds works on an abstract method
@Singleton
@Binds // @Binds, binds the Application instance to Context
abstract fun context(appInstance:Application): Context //just return the super-type you need
}
Step 2 : Remove redundant code from AppComponent
package com.mamak.geobaza.di
import android.app.Application
import dagger.BindsInstance
import dagger.Component
import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = [
ApiModule::class,
ViewModelModule::class,
AndroidSupportInjectionModule::class,
InterfaceModule::class,
ContextModule::class
])
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
@BindsInstance
fun apiModule(apiModule: ApiModule): Builder
@BindsInstance
fun interfaceModule(interfaceModule: InterfaceModule): Builder
// @BindsInstance //this two commented lines can be removed
// fun contextModule(contextModule: ContextModule): Builder
// why? because dagger already knows how to provide Context Module
fun build(): AppComponent
}
fun inject(appController: AppController)
}
Step 3: Modify the Component Builder to take advantage of @Component.Builder
and @Binds
package com.mamak.geobaza.di
import android.app.Application
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import javax.inject.Inject
class AppController : Application(), HasAndroidInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> {
return dispatchingAndroidInjector
}
override fun onCreate() {
super.onCreate()
DaggerAppComponent.Builder()
.application(this)
.apiModule(ApiModule())
.interfaceModule(InterfaceModule())
// .contextModule(ContextModule(this)) //this line can be removed
.build()
.inject(this)
}
}
Dagger 2, Providing Application Context in Module
This is how It is done. Using @BindsInstance in your component will inject application to all of your modules.In your case just to AppModule
@Singleton
@Component(modules = arrayOf(AppModule::class))
interface AppComponent {
@Component.Builder
interface Builder() {
fun build(): AppComponent
@BindsInstance
fun application(application: Application): Builder
}
}
** Delete code to "Provides application" function in your APP module and make sure you pass application context to create sharedPreferences.
@Module
class AppModule {
@Provides
@Singleton
fun provideSharedPrefManager(context: Application): SharedPreferencesManager =
SharedPreferencesManager(context)
}
and now in your onCreate of applicationClass
DaggerAppComponent.builder().application(this).build()
Optional: if you want to inject something into your applicationClass then do the following
DaggerAppComponent.builder().application(this).build().inject(this)
Injecting application context in library module with Dagger 2
Dagger 2 for Android comes to the rescue. It provides the concept of AndroidInjector
, which is a Component
that can be used to inject an instance in a static way, without having to know the dependency provider. Moreover, using the Dagger-
prefixed classes provided out of the box, the injected dependencies look like coming from nowhere. Awesome.
All you have to do is declare in the library a top-level Module
which is installed in the Application Component
. This Module
will provide all the dependencies and the SubComponent
s needed by the library, which will automatically inherit the @AppContext Context
that you seeded in the dependency graph, ready to be injected anywhere in you library, as well as every dependency you provide through the main Application Component
.
Here's a short example (written in Kotlin):
@Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
LibraryModule::class //plug-in the library to the dependency graph
])
@Singleton
interface AppComponent : AndroidInjector<App> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<App>() {
@BindsInstance
abstract fun appContext(@AppContext context: Context)
override fun seedInstance(instance: App) {
appContext(instance)
}
}
}
Edit: extended examples
An example of the Application subclass:
// DaggerApplication provides out-of-the-box support to all the AndroidInjectors.
// See the class' code to understand the magic.
public class App extends DaggerApplication {
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
// We only provide its own Injector, the Application Injector,
// that is the previous AppComponent
return DaggerAppComponent.builder().create(this);
}
And in your Android library:
@Module
public abstract class LibraryModule {
@ContributesAndroidInjector
public abstract LibraryActivity contributeLibraryActivityInjector();
}
public class LibraryActivity extends DaggerAppCompatActivity {
@Inject
@AppContext
Context appContext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceSate);
// here you automagically have your injected application context!
ExternalSingleton.getInstance(appContext)
}
}
How to inject Context into a Presenter using Dagger 2
You're using CustomApplication
with Dagger in your ApplicationComponent
, so that's what it knows about. It doesn't try to resolve types on its own, so Application
is some class Dagger never heard about.
You can either add another @Provides
/ @Binds
to bind CustomApplication > Application > Context
or just go the direct way and change your code to require a CustomApplication
instead of Application
:
@Provides
@Singleton
fun provideContext(application: CustomApplication): Context {
return application
}
// ... or alternatively ...
@Provides
@Singleton
fun provideApplication(application: CustomApplication): Application {
return application
}
@Provides
@Singleton
fun provideContext(application: Application): Context {
return application
}
Either way your application can then be used as a Context
.
Dagger 2 Inject Context in Kotlin Object
Is this because I am using an
object
(Singleton)?
Yes, object
s' properties are Java's static fields under the hood. Your Manager
class would decompile to something similar to:
public final class Manager {
@Inject
@NotNull
public static Context context;
public static final Manager INSTANCE;
static {
INSTANCE = new Manager();
}
private Manager() {
}
@NotNull
public final Context getContext() {
return context;
}
public final setContext(Context var1) {
context = var1;
}
}
and Dagger 2 simply does not support injection into static fields
. However, even if Dagger let you do it, the dependency wouldn't be satisfied. That's because Dagger won't be able to inject into an object unless is explicitly told to do so (like you do when injecting for example into activities) or creates the object by itself. Obviously the latter doesn't apply when it comes to Kotlin's object
s. In order to ask Dagger to inject the dependency you would have to somehow provide a MainApp
instance:
init {
mainApp.appComponent.inject(this)
}
which doesn't make much sense since you'd like to inject it (as a Context
) in the first place.
Therefore you have to either satisfy the dependency manually and don't bother with Dagger in this one or leave the object
idea behind, use just a standard class and let Dagger handle its scope:
@Singleton
class Manager @Inject constructor(private val context: Context) {
}
The only drawback is that you would have to inject a Manager
instance (created by Dagger) into every class that needs to use it instead of calling its methods statically.
Dagger failing to inject Application context
Injecting the repository into the viewmodel fixed this issue instead of trying to pass application context through the chain.
Related Topics
Check Whether Lock Was Enabled or Not
Hax Kernel Module Is Not Installed
How Might I Add a Watermark Effect to an Image in Android
Android - Periodic Background Service - Advice
How to Reduce the Spacing Between the Action Item Icons on Action Bar
Android App Integrated with Ok Google
How to Release Application Plugin Using Android Market
How to Customize the Action Mode's Color and Text
Soft Keyboard Shows Up on Edittext Focus Only Once
Errors Managing the Unityplayer Lifecycle in a Native Android Application
How Can Android Source Code Not Have a Main Method and Still Run