- Android で Dagger を使う(その1)
- Android で Dagger を使う(その2 : subcomponent)
- Android で Dagger を使う(その3 : Android Support)
Dependency Injection
API にアクセスするための interface が用意されているとします。- interface ApiService {
- ...
- }
ApiService をどうインスタンス化するかは Activity 側の責務ではないからです。
- public class MainActivity extends AppCompatActivity {
- // どうやってインスタンス化するかは知りたくない
- ApiService service;
- }
外部から注入すると
- テストなど、状況に応じて注入するインスタンスを切り替えられる
- インスタンス化の方法が変わっても利用側は影響をうけない
外部から注入する一番簡単な方法は、コンストラクタで渡すことです。 しかし Activity はフレームワークが生成するため、この方法はとれません。
↓これはできない
- public class MainActivity extends AppCompatActivity {
- ApiService service;
- public MainActivity(ApiService service) {
- this.service = service;
- }
- }
- public class MainActivity extends AppCompatActivity {
- ApiService service;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- service = ApiServiceFactory.create();
- setContentView(R.layout.activity_main);
- }
- }
- public class ApiServiceFactory {
- private static ApiService service;
- @NonNull
- public synchronized static ApiService create() {
- if (service == null) {
- final Retrofit retrofit = ...;
- return retrofit.create(ApiService.class);
- }
- return service;
- }
- }
この方法には以下のような欠点があります。
- 外部から注入したいクラスごとに同じようなFactoryクラスが必要
- どの Factory を使うかを結局知っていなくてはいけない
Dagger
Dagger (https://google.github.io/dagger) は Java で DI を行うことをサポートするライブラリです。「DI = Dagger を使うこと」ではありません。上記で書いたように Dagger を使わなくても DI はできます。
- dependencies {
- compile 'com.google.dagger:dagger:2.11'
- annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
- }
Component は injector です。inject される側(例だと Activity)は、Component を経由して必要なインスタンス(例だと ApiService のインスンタンス)をもらいます。
Module の責務はインスタンスの生成です。そのため、Module には inject するもの(例だと ApiService)をどうインスタンス化するかを定義します。
Module 用のクラスには @Module アノテーションをつけます。
inject するもの(ApiService のインスタンス)を返すメソッドを用意し、@Provides アノテーションをつけます。
- @Module
- public class ApiModule {
- @Provides
- public ApiService provideApiService() {
- final Retrofit retrofit = ...;
- return retrofit.create(ApiService.class);
- }
- }
Component は abstract class か interface で用意します。
@Component アノテーションをつけ、modules 属性に必要な Module クラスを指定します。
- @Component(modules = ApiModule.class)
- public interface AppComponent {
- ApiService apiService();
- }
- public final class DaggerAppComponent implements AppComponent {
- private Provider<ApiService> provideApiServiceProvider;
- private DaggerAppComponent(Builder builder) {
- assert builder != null;
- initialize(builder);
- }
- public static Builder builder() {
- return new Builder();
- }
- public static AppComponent create() {
- return new Builder().build();
- }
- @SuppressWarnings("unchecked")
- private void initialize(final Builder builder) {
- this.provideApiServiceProvider = ApiModule_ProvideApiServiceFactory.create(builder.apiModule);
- }
- @Override
- public ApiService apiService() {
- return provideApiServiceProvider.get();
- }
- public static final class Builder {
- private ApiModule apiModule;
- private Builder() {}
- public AppComponent build() {
- if (apiModule == null) {
- this.apiModule = new ApiModule();
- }
- return new DaggerAppComponent(this);
- }
- public Builder apiModule(ApiModule apiModule) {
- this.apiModule = Preconditions.checkNotNull(apiModule);
- return this;
- }
- }
- }
Module の @Provides がついたメソッドからは、対応する Factory が自動生成されます。例えば ApiModule の provideApiService() メソッドに対しては ApiModule_ProvideApiServiceFactory が自動生成されます。
- public final class ApiModule_ProvideApiServiceFactory implements Factory<ApiService> {
- private final ApiModule module;
- public ApiModule_ProvideApiServiceFactory(ApiModule module) {
- assert module != null;
- this.module = module;
- }
- @Override
- public ApiService get() {
- return Preconditions.checkNotNull(
- module.provideApiService(), "Cannot return null from a non-@Nullable @Provides method");
- }
- public static Factory<ApiService> create(ApiModule module) {
- return new ApiModule_ProvideApiServiceFactory(module);
- }
- }
AppComponent のインスタンスは自動生成される DaggerAppComponent.Builder を使って取得します。
- final AppComponent appComponent = DaggerAppComponent.builder()
- .apiModule(new ApiModule())
- .build();
- final AppComponent appComponent = DaggerAppComponent.create();
Android では Application クラスで Component を生成して保持しておくことが多いです。
- public class MyApplication extends Application {
- private AppComponent appComponent;
- @Override
- public void onCreate() {
- super.onCreate();
- appComponent = DaggerAppComponent.builder()
- .apiModule(new ApiModule())
- .build();
- }
- public AppComponent getAppComponent() {
- return appComponent;
- }
- }
- public class MainActivity extends AppCompatActivity {
- ApiService service;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- service = ((MyApplication) getApplication())
- .getAppComponent()
- .apiService();
- setContentView(R.layout.activity_main);
- }
- }
- public class MainActivity extends AppCompatActivity {
- ApiService apiService;
- HogeService hogeService;
- FugaService fugaService;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final AppComponent appComponent = ((MyApplication) getApplication())
- .getAppComponent();
- apiService = appComponent.apiService();
- hogeService = appComponent.hogeService();
- fugaService = appComponent.fugaService();
- setContentView(R.layout.activity_main);
- }
- }
まず AppComponent に inject される側を引数にとる void メソッドを定義します。
- @Component(modules = ApiModule.class)
- public interface AppComponent {
- void inject(MainActivity target);
- }
field の代入していた部分を上記で定義した inject メソッドを呼ぶように変更します。
- public class MainActivity extends AppCompatActivity {
- @Inject
- ApiService apiService;
- @Inject
- HogeService hogeService;
- @Inject
- FugaService fugaService;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ((MyApplication) getApplication())
- .getAppComponent()
- .inject(this);
- setContentView(R.layout.activity_main);
- }
- }
DaggerAppComponent の inject() を呼ぶと、MainActivity_MembersInjector を使って MainActivity の @Inject がついた field にインスタンスが代入されます。
- public final class DaggerAppComponent implements AppComponent {
- ...
- @SuppressWarnings("unchecked")
- private void initialize(final Builder builder) {
- this.provideApiServiceProvider = ApiModule_ProvideApiServiceFactory.create(builder.apiModule);
- this.mainActivityMembersInjector =
- MainActivity_MembersInjector.create(provideApiServiceProvider);
- }
- @Override
- public void inject(MainActivity target) {
- mainActivityMembersInjector.injectMembers(target);
- }
- ...
- }
- public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
- private final Provider<ApiService> apiServiceProvider;
- public MainActivity_MembersInjector(Provider<ApiService> apiServiceProvider) {
- assert apiServiceProvider != null;
- this.apiServiceProvider = apiServiceProvider;
- }
- public static MembersInjector<MainActivity> create(Provider<ApiService> apiServiceProvider) {
- return new MainActivity_MembersInjector(apiServiceProvider);
- }
- @Override
- public void injectMembers(MainActivity instance) {
- if (instance == null) {
- throw new NullPointerException("Cannot inject members into a null reference");
- }
- instance.apiService = apiServiceProvider.get();
- }
- public static void injectApiService(
- MainActivity instance, Provider<ApiService> apiServiceProvider) {
- instance.apiService = apiServiceProvider.get();
- }
- }
Singleton
Retrofit では毎回インスタンスを生成するとコストが高いので ApiService は Singleton にしたいのでした。ApiModule の provideApiService() に @Singleton アノテーションをつけると、provideApiService() で生成されたインスタンスは Singleton として保持されます。
- @Module
- public class ApiModule {
- @Singleton
- @Provides
- public ApiService provideApiService() {
- final Retrofit retrofit = ...;
- return retrofit.create(ApiService.class);
- }
- }
どの Component が管理するかを指定するために、管理する Component にも @Singleton をつけます。
- @Component(modules = ApiModule.class)
- @Singleton
- public interface AppComponent {
- ...
- }
- public final class DaggerAppComponent implements AppComponent {
- ...
- @SuppressWarnings("unchecked")
- private void initialize(final Builder builder) {
- this.provideApiServiceProvider =
- DoubleCheck.provider(ApiModule_ProvideApiServiceFactory.create(builder.apiModule));
- ...
- }
- }
- @Scope
- @Documented
- @Retention(RUNTIME)
- public @interface Singleton {}
テスト
テスト時に mock 化した ApiService インスタンスを注入することを考えましょう。まず MyApplication が持つ AppComponent を差し替えられるように、テスト用のメソッドを用意します。
- public class MyApplication extends Application {
- private AppComponent appComponent;
- ...
- @VisibleForTesting
- public void setAppComponent(AppComponent appComponent) {
- this.appComponent = appComponent;
- }
- }
- public class MockApiModule extends ApiModule {
- private final ApiService service;
- public MockApiModule(ApiService service) {
- this.service = service;
- }
- @Override
- public ApiService provideApiService() {
- return service;
- }
- }
- final AppComponent appComponent = DaggerAppComponent.builder()
- .apiModule(new MockApiModule(mockApiService))
- .build();
こうすることで MainActivity には MockApiModule を介して mock 化された ApiService インスタンスが注入されます。
- @RunWith(AndroidJUnit4.class)
- public class MainActivityUiTest {
- @Rule
- ActivityTestRule<MainActivity> activityTestRule
- = new ActivityTestRule<>(MainActivity.class, false, false);
- @Test
- public void test() throws Exception {
- final Context context = InstrumentationRegistry.getTargetContext();
- final ApiService mockApiService = mock(ApiService.class);
- ...
- final AppComponent appComponent = DaggerAppComponent.builder()
- .apiModule(new MockApiModule(mockApiService))
- .build();
- ((MyApplication) context.getApplicationContext())
- .setAppComponent(appComponent);
- activityTestRule.launchActivity(new Intent(context, MainActivity.class));
- ...
- }
- }
余談
Subcomponent 使ってません。Activity ごとに Subcomponent にわけるべきなのか私にはまだわかりません。
Subcomponent に分けるということは Component が1つの場合にくらべて明らかに複雑性は増します。
そのデメリットを上回るメリットがわかりませんでした。
(MVPとかMVVMではメリットあるのかな?Androidの思想に沿った設計が好きでそういうの取り入れてないのでわからないです)
0 件のコメント:
コメントを投稿