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〜
で始めることになっています。
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されたインスタンスが渡されます。
package ko2ic.dagger2.infrastructure;
@Module
public class InfraLayerModule {
@Provides
public WeatherRepository provideWeatherRepository(Context context) {
return new WeatherRepository(context);
}
}
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
で宣言したメソッドの戻り値(インスタンス)の中から解決するためのクラスになります。
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で利用できるようにします。
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;
}