Dagger2 導入Step By Step

  • 95
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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