I am using Dagger 2 and have it working however I now need access to the Android Application Context.
Its not clear to me how to inject and get access to the context. I have tried to do this as follows:
#Module
public class MainActivityModule {
private final Context context;
MainActivityModule(Context context) {
this.context = context;
}
#Provides #Singleton
Context provideContext() {
return context;
}
}
However this results in the following exception:
java.lang.RuntimeException: Unable to create application : java.lang.IllegalStateException: mainActivityModule must be set
If I inspect the Dagger generated code this exception is raised here:
public Graph build() {
if (mainActivityModule == null) {
throw new IllegalStateException("mainActivityModule must be set");
}
return new DaggerGraph(this);
}
I am not sure if this is the correct way to get Context injected - any help will be greatly appreciated.
#Module
public class MainActivityModule {
private final Context context;
public MainActivityModule (Context context) {
this.context = context;
}
#Provides //scope is not necessary for parameters stored within the module
public Context context() {
return context;
}
}
#Component(modules={MainActivityModule.class})
#Singleton
public interface MainActivityComponent {
Context context();
void inject(MainActivity mainActivity);
}
And then
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule(MainActivity.this))
.build();
It took me a while to find a proper solution, so thought it might save some time for others, as far as I could gather this is the preferred solution with the current Dagger version (2.22.1).
In the following example I need the Application's Context to create a RoomDatabase (happens in StoreModule).
Please if you see any errors or mistakes let me know so I'll learn as well :)
Component:
// We only need to scope with #Singleton because in StoreModule we use #Singleton
// you should use the scope you actually need
// read more here https://google.github.io/dagger/api/latest/dagger/Component.html
#Singleton
#Component(modules = { AndroidInjectionModule.class, AppModule.class, StoreModule.class })
public interface AwareAppComponent extends AndroidInjector<App> {
// This tells Dagger to create a factory which allows passing
// in the App (see usage in App implementation below)
#Component.Factory
interface Factory extends AndroidInjector.Factory<App> {
}
}
AppModule:
#Module
public abstract class AppModule {
// This tell Dagger to use App instance when required to inject Application
// see more details here: https://google.github.io/dagger/api/2.22.1/dagger/Binds.html
#Binds
abstract Application application(App app);
}
StoreModule:
#Module
public class StoreModule {
private static final String DB_NAME = "aware_db";
// App will be injected here by Dagger
// Dagger knows that App instance will fit here based on the #Binds in the AppModule
#Singleton
#Provides
public AppDatabase provideAppDatabase(Application awareApp) {
return Room
.databaseBuilder(awareApp.getApplicationContext(), AppDatabase.class, DB_NAME)
.build();
}
}
App:
public class App extends Application implements HasActivityInjector {
#Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
// Using the generated factory we can pass the App to the create(...) method
DaggerAwareAppComponent.factory().create(this).inject(this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
I have read this article and it was very helpful.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
Sample code.
Update: I removed from AppComponent.kt these lines because are not necessaries
fun context(): Context
fun applicationContext(): Application
AppComponent.kt
#Singleton
#Component(
modules = [
NetworkModule::class,
AppModule::class
]
)
interface AppComponent {
fun inject(viewModel: LoginUserViewModel)
}
AppModule.kt
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun providesApplication(): Application = application
#Provides
#Singleton
fun providesApplicationContext(): Context = application
#Singleton
#Provides
fun providesNetworkConnectivityHelper(): NetworkConnectivityHelper{
return NetworkConnectivityHelper(application.applicationContext)
}
}
NetworkConnectivityHelper.kt
And only added #Inject constructor to pass the Context
class NetworkConnectivityHelper #Inject constructor(context: Context) {
private val connectivityManager =
context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
#Suppress("DEPRECATION")
fun isNetworkAvailable(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val nc = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
nc != null
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnected
}
}
App class.kt
class App : Application() {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
this.appComponent = this.initDagger()
}
private fun initDagger() = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
Finally in my Activity I injected my helper
#Inject lateinit var networkConnectivity: NetworkConnectivityHelper
And YEI! it works for me.
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
probably we could inject the context as shown below:
the application component
#Component(
modules = [
(ApplicationModule::class),
(AndroidSupportInjectionModule::class),
(UiBindingModule::class)
]
)
interface ApplicationComponent : AndroidInjector<AndroidApplication> {
override fun inject(application: AndroidApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: AndroidApplication): Builder
#BindsInstance
fun context(context: Context): Builder
fun build(): ApplicationComponent
}
}
the custom application extending dagger application
class AndroidApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerApplicationComponent.builder().application(this).context(this).build()
}
}
Example ApplicationModule
#Module
abstract class ApplicationModule {
/**
* Binds a background thread executor, which executes threads from a thread pool
* #param jobExecutor
* #return
*/
#Binds
internal abstract fun provideThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor
/**
* Binds main ui looper thread
* #param uiThread
* #return
*/
#Binds
internal abstract fun providePostExecutionThread(uiThread: UIThread): PostExecutionThread
}
Example UI BindingModule
#Module
abstract class UiBindingModule {
#ContributesAndroidInjector(modules = [(MainActivityModule::class)])
internal abstract fun mainActivity(): MainActivity
#ContributesAndroidInjector(modules = [(MapFragmentModule::class)])
internal abstract fun mapFragment(): MapFragment
}
#Singleton
#Component(modules = [YourModule::class, ThatOtherModule::class])
interface ApplicationComponent {
#Component.Builder
interface Builder {
#BindsInstance fun applicationContext(applicationContext: Context): Builder
fun build(): ApplicationComponent
}
}
class YourApplication : Application() {
val component: ApplicationComponent by lazy {
DaggerApplicationComponent.builder()
.applicationContext(applicationContext)
.build()
}
}
Use the #BindInstance to declare a abstract function that provides the context dependency. i.e #BindsInstance fun applicationContext(applicationContext: Context): Builder
set the context using .applicationContext(applicationContext)
Related
I'm creating an application in Kotlin using the MVP pattern.
I would need to inject a Repository into my Presenter for this purpose. Except that for this, my Repository requires a Retrofit interface as a parameter of its constructuor.
I'm a beginner in the use of Dagger2, and the answers found on the internet are far too complicated for such a basic case like mine.
Here's the repository i want to be injected :
class RepositoryInventory(private val api: Service): IRepositoryInventory {
override fun getInventoryItemByNum(itemnum: String): Observable<Response<Item>> {
return api.getInventoryItemByNum(itemnum)
.toObservable()
}
override fun getAllInventoryItems(): Single<Response<Item>> {
return api.getAllInventoryItems()
}
}
My Component
#Singleton
#Component(modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
fun inject(loginActivity: LoginActivity)
fun inject(itemDetailActivity: ItemDetailActivity)
}
My module :
#Module
class ActivityModule(private var activity: Activity) {
#Provides
fun provideActivity(): Activity {
return activity
}
#Provides
fun provideLoginPresenter(): LoginPresenter {
return LoginPresenter()
}
#Provides
fun provideItemDetailPresenter(): ItemDetailPresenter {
return ItemDetailPresenter()
}
}
In my activity, my module is injected with this method :
private fun injectDependency() {
val activityComponent = DaggerActivityComponent.builder()
.activityModule(ActivityModule(this))
.build()
activityComponent.inject(this)
}
I have 2 components and 2 modules: one designed to inject into a fragment and the other into an activity.
Except in my case, I want to inject into a Presenter that is not a Fragment or an Activity but a class
Ok, my guess is you want to inject RepositoryInventory into LoginPresenter. If so, you can make use of #ContributesAndroidInjector and Binds
First, create a LoginActivityModule
#Module
abstract class LoginActivityModule {
#Binds
abstract fun loginPresenter(loginPresenter: LoginPresenter): LoginPresenter
}
Then, create a module called ActivityBindingModule
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = [LoginActivityModule::class])
abstract fun loginActivity(): LoginActivity
#ContributesAndroidInjector()
abstract fun itemDetailActivity(): ItemDetailActivity
}
And change your ActivityComponent like this
#Singleton
#Component(modules = arrayOf(ActivityModule::class, ActivityBindingModule::class))
interface ActivityComponent {
}
And in your LoginPresenter:
class LoginPresenter #Inject constructor(private val repositoryInventory: IRepositoryInventory) {
...
}
Remember to remove this in ActivityModule:
#Provides
fun provideLoginPresenter(): LoginPresenter {
return LoginPresenter()
}
I'm trying to write a UI test using Espresso, I have a view model that I need to mock. But inside this mocked viewmodel there're several #Inject fields that are null, which causing all sorts of trouble.
Here's the view model itself (Names changed and stuff for simplicity but the code is exactly the same)
public class MyViewModel extends ViewModel {
private final MyFieldRepository myFieldRepository;
#Inject
public MyViewModel(MyFieldRepository myField) {
this.myFieldRepository = myField; //remains null
}
public void start() { //NPE here
myFieldRepository.startAPI() //... async call code
}
}
And the view model Dagger Module
#Module
public class MyViewModelModule {
#Provides
#IntoMap
#ViewModelKey(MyViewModel.class)
public ViewModel providesMyViewModel() {
MyViewModel myViewModel = Mockito.mock(MyViewModel.class);
return pinPadViewModel;
}
}
And finally the field module (that I wanted to be injected)
#Module
public class MyFieldRepositoryModule {
#Provides
#AppScoped
public MyFieldRepository providesMyField() {
MyFieldRepository myField = Mockito.mock(MyFieldRepository.class);
return myField;
}
}
And here's the component code:
#AppScoped
#Component(modules = {
MyViewModelModule.class,
MyFieldModule.class,
})
public interface TestAppComponent extends AndroidInjector<TestApp> {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application application);
TestAppComponent build();
}
}
I want to find a way for myField to be injected by Dagger using MyFieldModule.
You have to Use ViewModelProvider.Factory to provide dependency
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}
On Android, When using Dagger2, I have to call the following line on every activities that uses apiService:
#Inject
public ApiService apiService;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerApiComponent.builder()
.activityModule(new ActivityModule(this))
.build()
.inject(this);
//...
}
How can I summarize it to something like:
DaggerApiComponent.builder()
.activity(this)
.build()
.inject(this);
or even simpler to something like:
MyApplication.injectApiService(this);
How should I change my component and modules to use Dagger2 with less copy-pasting code in my activities?
Here is my ApiComponent:
#Singleton
#Component(modules = ApiModule.class)
public interface ApiComponent {
void inject(MainActivity activity);
void inject(...
}
Here is ApiModule:
#Module(includes = {RetrofitModule.class, ActivityModule.class})
public class ApiModule {
#Singleton
#Provides
public static ApiService provideApiService(Activity activity) {
//...
}
}
and ActivityModule:
#Module
public class ActivityModule {
private final Activity context;
public ActivityModule(Activity context) {
this.context = context;
}
#Singleton
#Provides
public Activity provideActivityContext() {
return context;
}
}
The approach of such "DI" has two problems:
like what the OP said: we need to copy & paste this boilerplate wherever we need injection, which is tedious and hard to refactor.
Injectee (e.g. Activity) should not know where the #Inject instance is from, what it cares about is just "hey, give me an instance of it".
To resolve above problems, dagger.android comes to the rescue.
Create AndroidInjector for each component.
// App component
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class, // build-in module
ActivityBindingModule::class,
AppModule::class
]
)
interface AppComponent : AndroidInjector<MainApplication> {
// we need to bind `MainApplication` instance to this component,
// so we have a builder here.
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MainApplication>()
}
// Each controller (e.g. `Activity` / `Fragment` / `Service`) subcomponents
#Module
abstract class ActivityBindingModule {
// will generate a FooActivitySubcomponent under ActivityBindingModule's component
#ActivityScoped
#ContributesAndroidInjector(modules = [FooModule::class])
internal abstract fun fooActivity(): FooActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [BarModule::class])
internal abstract fun barActivity(): BarActivity
}
Wire up your AndroidInjectors, so it can do injection for us using provided injector in step 1.
// App component
class MainApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
}
// Each controller subcomponents
class FooActivity : DaggerAppCompatActivity() {
#Inject lateinit var foo: Foo
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// no need to call inject() here anymore!
foo.doSth()
}
}
For a concrete example: check out iosched
You can use the Android Activity injector, the usage is described well here.
I'm a Dagger newb and have a trouble with using it.
What I want to develop is that using RxAndroidBle and to initialize it by Dagger for providing Context.
So I researched how it can be implemented, and I wrote some codes and It seems to be working for me but not working at all.
The followings are my codes.
AppComponent.kt
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
#Named("appContext")
#Singleton
fun provideContext(application: Application): Context =
application.applicationContext
}
BluetoothModule.kt
#Module
class BluetoothModule {
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient(#Named("appContext") context: Context):RxBleClient =
RxBleClient.create(context)
}
BluetoothController.kt for injecting by DaggerApplication.
class BluetoothController : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}
I've inserted
android:name".BluetoothController"
to AndroidManifest.xml
And this is how I would use it.
#field:[Inject Named("rxBleClient")]
lateinit var rxBleClient: RxBleClient
But it always occurs an error says: lateinit property context has not been initialized
What things I've missed? Can anyone help me?
Thanks in advance.
Add the below code to make this happen.
Create ActivityBuilderModule for injecting within the activity. Consider our activity as MainActivity
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules=[MainActivityModule::class])
abstract fun contributeSplashActivity(): MainActivity
}
Create your MainActivityModule
#Module
class MainActivityModule{
#Provides()
fun contributeSplashActivity(mainActivity: MainActivity):
MainActivity=mainActivity
}
Modify your component.
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
ActivityBuilderModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
Within MainActivity just inject.
class MainActivity{
...
#Inject
lateinit var rxBleClient: RxBleClient
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
}
Let us know in case of any issue.
the context object is not initialized before its called
Though i dont know, how chained your initialization are.
Use #Inject to add deppendencies, do something like this
#Module
class BluetoothModule(val context : Context) {
//#Inject private lateinit var context : Context
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient():RxBleClient =
RxBleClient.create(context)
}
let your call be like this
val component = AppComponent()
component.bluetoothModule(appContext)
.//other calls here
.build()
I've been checking recently Dagger 2.14.1 with the new Android injectors.
I'm using MVP and the Presenter is getting inject into the View correctly:
class CustomApplication : Application(), HasActivityInjector {
#Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent
.builder()
.create(this)
.inject(this)
}
override fun activityInjector(): DispatchingAndroidInjector<Activity> {
return activityDispatchingAndroidInjector
}
}
--
#Singleton
#Suppress("UNUSED")
#Component(modules = arrayOf(AndroidInjectionModule::class, ApplicationModule::class, ActivityBuilder::class))
interface ApplicationComponent : AndroidInjector<CustomApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<CustomApplication>()
override fun inject(application: CustomApplication)
}
--
#Module
class ApplicationModule {
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
}
--
#Module
#Suppress("UNUSED")
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = arrayOf(ActivitiesModule::class))
internal abstract fun bindSplashActivity(): SplashActivity
}
--
#Singleton
class SplashPresenter #Inject constructor() {
fun test() {
Log.d("TAG", "this is a test")
}
}
Now, instead of having the logged message harcoded, I would like to get it from string.xml, so I tried this:
#Singleton
class SplashPresenter #Inject constructor(private val context: Context) {
fun test() {
Log.d("TAG", context.getString(R.strings.test))
}
}
But then I get this error:
Error:(7, 1) error: [dagger.android.AndroidInjector.inject(T)]
android.app.Application cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
Could anyone tell me please how to inject the app context (or the resources) into the presenter?
Thanks.
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.