Implementing a Simple Dagger2 Sample

Implementing a simple Dagger2 sample

AppModule.kt: Provide the application context. No need to write @singleton @provides for your Test* classes (will see why)

@Module
class AppModule {
@Provides
@Singleton
fun provideApplication(app: App): Context = app.applicationContext
}

AppComponent.kt: @Component.Builder is deprecated IIRC. Use @Component.Factory. And replace AndroidInjectionModule::class with AndroidSupportInjectionModule::class since we are using dagger-android-support and android's *Compat* stuff. Refer a new module here called ActivityModule::class.

@Singleton
@Component(modules = [
ActivityModule::class
AndroidSupportInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {

@Component.Factory
abstract class Factory : AndroidInjector.Factory<App>
}

TestClass.kt & TestOperator.kt: Since you were providing singletons by writing @singleton and @provides method, I assume you want them to be singletons. Just annotate the class definition with @Singleton and dagger will take care of it. No need to write @Provides methods.

@Singleton
class TestClass @Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}

@Singleton
class TestOperator @Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}

App.kt: Using factory instead of builder since @Component.Builder is deprecated.

class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this)
}
}

ActivityModule.kt: Provide a module to dagger to create your activities.

@Module
interface ActivityModule {

@ContributesAndroidInjector
fun provideMainActivity(): MainActivity
}

MainActivity.kt: Finally, extend from DaggerAppCompatActivity.

class MainActivity : DaggerAppCompatActivity() {

@Inject
lateinit var testClass: TestClass

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}

I believe this should run without issues. For more reference you could look into this sample and the new simpler docs at dagger.dev/android

Simplest way to create a Singleton w/ Dagger 2?

You only need modules for things that you can't annotate with @Inject constructor (because for example, the framework creates it for you - like context). If you can't add an @Inject constructor, you also need to specify a void inject(...) method in the component as well.

However, if you can create it with a @Inject constructor, then @Inject works as a field annotation as well

@Component(modules={ContextModule.class})
@Singleton
public interface SingletonComponent {
void inject(MainActivity mainActivity);
}

@Module
public class ContextModule {
Context context;

public ContextModule(Context context) {
this.context = context;
}

@Provides
Context context() {
return context;
}
}

@Singleton
public class MyOtherSingleton {
@Inject
public MyOtherSingleton() {
}
}

@Singleton
public class MySingleton {
@Inject Context context;
@Inject MyOtherSingleton myOtherSingleton;

@Inject
public MySingleton() {
}
}

You can also do constructor parameters

private final Context context;
private final MyOtherSingleton myOtherSingleton;

@Inject
public MySingleton(Context context, MyOtherSingleton myOtherSingleton) {
this.context = context;
this.myOtherSingleton = myOtherSingleton;
}

SingletonComponent singletonComponent = DaggerSingletonComponent.builder()
.contextModule(CustomApplication.this)
.build();

// and elsewhere

@Inject
MySingleton mySingleton;

// ...
singletonComponent.inject(this);

Verified to work:

08-23 04:39:28.418 com.zhuinden.rxrealm D/DogView: My singleton has [com.zhuinden.rxrealm.application.CustomApplication@53348a58] and [com.zhuinden.rxrealm.application.injection.test.MyOtherSingleton@5336bb74]
08-23 04:39:36.422 com.zhuinden.rxrealm D/CatView: My singleton has [com.zhuinden.rxrealm.application.CustomApplication@53348a58] and [com.zhuinden.rxrealm.application.injection.test.MyOtherSingleton@5336bb74]

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

Dagger 2 and interface implementations

Unlike with constructor injection, the @Inject annotated fields of dependencies constructed in @Provides methods can't be automatically injected. Being able to inject fields requires a component that provides the type of the field in its modules, and in the provider methods themselves, such an implementation is not available.

When the presenter field is injected in MainActivity, all that happens is the provider method is called and presenter is set to its return value. In your example, the no-args constructor does no initialization, and neither does the provider method, so no initialization takes place.

The provider method does however have access to instances of other types provided in the module via its parameters. I think using parameters in the provider method is in fact the suggested (even the only) way to "inject" the dependencies of the provided type, because it explicitly indicates them as dependencies within the module, which allows Dagger to throw an error at compile-time if they can't be satisfied.

The reason it doesn't currently throw an error is because MainPresenterImpl could get its OkHttpClient dependency satisfied if MainPresenterImpl and not MainPresenter was somewhere a target for injection. Dagger can't make a members-injection method for the interface type, because as an interface, it can't have injectable fields, and it won't automatically inject the fields of the implementing type, because it's just supplying whatever the provider method returns.

Dagger2 dependency Cycle by Using @Binds and @Inject fields

@Binds is used to let dagger know the different implementations of an interface. You don't need @Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have @Inject constructor, which means dagger can instantiate them and we don't need to write extra @Provides functions for those classes.

@Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add @XScope with @Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.

As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).

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)
}
}


Related Topics



Leave a reply



Submit