Dagger 2 & Android
의존성 주입(DI)을 이용하여 안드로이드 앱을 만드는 데 가장 큰 어려움 중 하나는 Activity나 Fragment 같은 많은 안드로이드 프레임워크 클래스들이 OS에 의해 인스턴스화 된다는 점입니다.
하지만 Dagger는 이러한 안드로이드 프레임워크 클래스들도 주입할 수 있습니다.
구글링을 해보면 예전에 만들어진 예제들이 많은데, 가이드에서는 공식적인 dagger.android을 이용하기 권장합니다.
Activity 인스턴스와 SharedPreferenceManager 인스턴스를 Dagger를 이용해 주입하는 예제를 dagger.android API를 이용해 만들었습니다.
android {
...
android {
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
}
}
}
dependencies {
...
// dagger
compile 'com.google.dagger:dagger:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
provided 'javax.annotation:jsr250-api:1.0'
// dagger android
compile 'com.google.dagger:dagger-android:2.11-rc2'
compile 'com.google.dagger:dagger-android-support:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.11-rc2'
}
최신 릴리즈 정보는 github에서 확인할 수 있습니다.
https://github.com/google/dagger/releases
먼저 의존성을 요청할 @Inject annotation을 선언합니다.
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import javax.inject.Inject;
import shlee.myapplication.android.SharedPreferenceManager;
public class MainActivity extends AppCompatActivity {
@Inject
SharedPreferenceManager sharedPreferenceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferenceManager.setName("dagger test");
String name = sharedPreferenceManager.getName();
}
}
import android.content.Context;
import android.content.SharedPreferences;
import javax.inject.Inject;
import javax.inject.Singleton;
import shlee.myapplication.MainActivity;
@Singleton // Application scope
public class SharedPreferenceManager {
private SharedPreferences sharedPreferences;
@Inject
SharedPreferenceManager(MainActivity activity) {
sharedPreferences = activity.getSharedPreferences("demo", Context.MODE_PRIVATE);
}
public void setName(String name) {
sharedPreferences.edit().putString("Name", name).apply();
}
public String getName() {
return sharedPreferences.getString("Name", "default");
}
}
@Inject annotation에 대한 좀 더 자세한 설명은 이전 글에 있습니다.
이전 글에서는 Module에서 @Provide 메서드를 통해 어떻게 의존성을 구성하고 제공하는지를 정의했었습니다. 구글링 해보면 Activity 클래스도 @Provide 메서드를 통해 정의할 수 있지만, 앞서 기재했듯이 dagger.android API를 이용하여 만들어 보겠습니다.
먼저 MainActivity를 주입하기 위해 AndroidInjector<MainActivity>를 구현하는 @Subcomponent를 작성하고, AndroidInjector.Builder<MainActivity>를 상속받는 @Subcomponent.Builder를 작성합니다.
AndroidInjector<T>는 간단히 말하면 안드로이드 프레임워크 클래스들(Application, Activity, Fragment, Service, BroadcastReceiver, ContentProvider)을 주입시켜주는 클래스입니다.
import javax.inject.Singleton;
import dagger.Subcomponent;
import dagger.android.AndroidInjector;
import shlee.myapplication.MainActivity;
@Singleton // SharedPreferenceManager is @Singleton class.
@Subcomponent
public interface MainActivityComponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
MainActivity를 주입하기 위해 몇 가지 구현 요소가 더 필요합니다.
android.app.Application을 상속받는 App 클래스 구현
App을 주입하기 위해 AndroidInjector<App>을 구현하는 @Component를 작성하고 AndroidInjector.Builder <App>을 상속받는 @Component.Builder를 작성합니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="shlee.myapplication">
<application
android:name=".android.App"
android:allowBackup="true"
....
</application>
</manifest>
import android.app.Application;
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
}
}
import dagger.Component;
import dagger.android.AndroidInjector;
@Component(modules = {Module.class})
interface AppComponent extends AndroidInjector<App>{
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<App> {}
}
Module을 작성할 때 subcomponent인 MainActivityComponent를 더하고, MainActivityComponent.Builder에 바인드 합니다.
import android.app.Activity;
import dagger.Binds;
import dagger.android.ActivityKey;
import dagger.android.AndroidInjector;
import dagger.multibindings.IntoMap;
import shlee.myapplication.MainActivity;
@dagger.Module(subcomponents = MainActivityComponent.class)
abstract class Module {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindMainActivityInjectorFactory(MainActivityComponent.Builder builder);
}
import dagger.Component;
import dagger.android.AndroidInjector;
@Component(modules = {Module.class})
interface AppComponent extends AndroidInjector<App>{
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<App> {}
}
@Inject, @Module, @Component를 모두 구성하고 build를 하면 AppComponent를 implement한 DaggerAppComponent가 자동 생성됩니다.
DaggerAppComponent를 build/create 하기 전에 몇 가지 작업이 더 필요합니다.
App 클래스에 HasActivityInjector interface를 implement 하고 activityInjector() 메서드에서 return 하기 위해 DispatchingAndroidInjector<Activity>를 @Inject 합니다.
MainActivity 클래스의 onCreate() 메서드에서 super.onCreate(savedInstanceState) 이전에 AndroidInjection.inject(this)를 호출합니다.
import android.app.Activity;
import android.app.Application;
import javax.inject.Inject;
import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector;
import dagger.android.HasActivityInjector;
public class App extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().create(this).inject(this);
}
@Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
}
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
import shlee.myapplication.android.SharedPreferenceManager;
public class MainActivity extends AppCompatActivity {
@Inject
SharedPreferenceManager sharedPreferenceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferenceManager.setName("dagger test");
String name = sharedPreferenceManager.getName();
}
}
마지막으로 DaggerAppComponent를 create 하고 App을 inject 합니다.
실제로 의존성(MainActivity, SharedPreferenceManager 인스턴스)를 주입하는 부분은 Dagger가 담당하게 됩니다.
https://google.github.io/dagger//android.html
Dagger2가 아직은 Beta 단계인 API가 많습니다.
그러다 보니 구글링을 해봐도 최신 API를 이용한 예제가 없네요.
공식 가이드 문서나, github에도 설명이 부족합니다.
무엇보다도 쓰기 굉장히 복잡합니다ㅠ 특히 안드로이드 주요 구성 요소들과 결합하면 더 복잡해지네요...
Activity 하나를 주입시키기 위해 만들어야 하는 클래스, 인스턴스와 관련된 내용들을 이해하려면 쉽지 않습니다.
우선 간단한 부분부터 Dagger를 이용하고 API의 버전이 올라가서 직관성이 좋아지면 더 많이 쓸 계획입니다.