Edited at

Dagger2 導入Step By Step

More than 3 years have passed since last update.

Dagger2を組み込む方法と要素を段階的に説明します。

Dagger1との比較も少し触れます。


Step1 gradle設定

build.gradle でbuildscriptにapt(Annotation Processing Tool)のgradle pluginのclasspathを追加します。

これは、Dagger2がソースを生成する仕組みなので必要になります。

 buildscript {

repositories {
jcenter()
+ mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
+ classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}

app/build.gradle でdagger2を追加します

 apply plugin: 'com.android.application'

+apply plugin: 'android-apt'

android {
compileSdkVersion 23
buildToolsVersion "23.0.1"

defaultConfig {
applicationId "ko2ic.dagger2"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'de.greenrobot:eventbus:2.4.0'
compile 'com.mcxiaoke.volley:library:1.0.19'
compile 'com.jakewharton:butterknife:7.0.1'

+ compile 'com.google.dagger:dagger:2.0.2'
+ apt 'com.google.dagger:dagger-compiler:2.0.2'
+ provided 'javax.annotation:jsr250-api:1.0'
}


Step2 Moduleクラス

Moduleクラスを作成します。Moduleクラスは@Provides アノテーションを付けたメソッドを持ちます。

@Provides を付けたメソッドの戻り値がDIされるときのインスタンスになります。

例えば、@Inject Context context であれば、Moduleクラス(以下の例ではAppModule)のprovideApplicationContext() メソッドが呼ばれます。

次に、命名規約についての説明です。

Moduleクラスの名前はModuleで終え(〜Module)、 @Provides を付けるメソッドの名前は、provide〜 で始めることになっています。


AppModule.java

package ko2ic.dagger2;

import android.app.Application;
import android.content.Context;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

@Module
public class AppModule {
private final Application application;

public AppModule(Application application) {
this.application = application;
}

@Provides
@Singleton
Context provideApplicationContext() {
return application.getApplicationContext();
}
}


Moduleはレイヤーごとに作っておくと分かりやすいかもしれません。

@Provides なメソッドの引数にはDIされたインスタンスが渡されます。


InfraLayerModule.java

package ko2ic.dagger2.infrastructure;

@Module
public class InfraLayerModule {
@Provides
public WeatherRepository provideWeatherRepository(Context context) {
return new WeatherRepository(context);
}
}



AppLayerModule.java

package ko2ic.dagger2.application;

@Module
public class AppLayerModule {
@Provides
public WeatherFacade provideWeatherFacade(WeatherRepository repository) {
return new WeatherFacade(repository);
}
}



Step3 Componentインターフェイス

Componentインターフェイスを作成します。これがDagger1と2の違いになります。

ComponentはDagger1のObjectGraphになり得るものです。

Dagger2ではこのComponentの実装クラス(この場合はDaggerAppComponent)が自動生成されます。

@Inject 宣言した時に注入するクラスを @Module@Provides で宣言したメソッドの戻り値(インスタンス)の中から解決するためのクラスになります。


AppComponent.java

package ko2ic.dagger2;

import javax.inject.Singleton;

import dagger.Component;
import ko2ic.dagger2.application.AppLayerModule;
import ko2ic.dagger2.infrastructure.InfraLayerModule;
import ko2ic.dagger2.ui.activity.SecondActivity;

@Singleton
@Component(modules = {InfraLayerModule.class, AppLayerModule.class, AppModule.class})
public interface AppComponent {
void inject(SecondActivity activity);
}


例えばComponentインターフェイスで



  • Foo getFoo(); を宣言するのは、Dagger1でのobjectGraph.get(Foo.class); と同じになります。


  • void inject(Baz baz); を宣言するのは、Dagger1でのobjectGraph.inject(baz); と同じになります。


Step4 Applicationサブクラス

Applicationのサブクラスを作ります。

Component(ObjectGraph)を構築して、Activityで利用できるようにします。


AppApplication.java

public class AppApplication extends Application {

private AppComponent applicationComponent;

@Override
public void onCreate() {
super.onCreate();
initializeInjector();
}

private void initializeInjector() {
applicationComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}

public AppComponent getApplicationComponent() {
return applicationComponent;
}
}


initializeInjector() で全てのモジュールを記述していないことが不思議かもしれません。

要は applicationComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).appLayerModule(new AppLayerModule()).infraLayerModule(new InfraLayerModule()).build(); と何故記述しないのか。

実は、Moduleが引数なしのコンストラクタの場合は記述しなくても良いのです。これはDagger1の時も同様です。

AndroidManifest.xml

     <application

+ android:name=".AppApplication"
android:allowBackup="true"


Step5 Dependency Injection

インスタンスしているところをDIするように変更します。

こうすることで、


  • Androidに依存する必要のないFacadeなどがPOJOになります。

  • また、単体テスト時にModuleクラスを変えることで簡単にモッククラスに変更できるようになります。

この例ではレイヤーになるクラスに明示的に interface を作りません。

今の時代Mockitoがあるので、実装クラスでも簡単にモック化できます。

ポリモーフィズムが必要になるときだけインターフェイスを作ればいいと思います。

 public class SecondActivity extends AppCompatActivity {

- private WeatherFacade facade;
+ @Inject
+ WeatherFacade facade;

@Bind(R.id.editText)
EditText editText;

@Bind(R.id.button)
Button button;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
ButterKnife.bind(this);
+ getApplicationComponent().inject(this);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- facade = new WeatherFacade(getApplicationContext());
facade.fetchWeahter(editText.getText().toString());
}
});
}
+ private AppComponent getApplicationComponent() {
+ return ((AppApplication) getApplication()).getApplicationComponent();
+ }

 public class WeatherFacade {

private WeatherRepository repository;

- private Context context;

- public WeatherFacade(Context context){
- this.context = context;
+ @Inject
+ public WeatherFacade(WeatherRepository repository){
+ this.repository = repository;
}

public void fetchWeahter(String cityCode){
- repository = new WeatherRepository(context);
repository.fetchWeather(cityCode);
}
}

 public class WeatherRepository {

private Context mContext;

+ @Inject
public WeatherRepository(Context context) {
mContext = context;
}