LoginSignup
146
145

More than 5 years have passed since last update.

俺的 Dagger ベストプラクティス

Last updated at Posted at 2015-04-08

Dagger

Dagger は Android/Java で動作する依存性注入(Dependency Injection)ライブラリです。
結構 Dagger を使っているプロジェクトはあるのですが、設計やその利用方法に疑問点や改善点が散見されるので、まとめておこうと思います。

Dagger 2がリリースされていますが、@ModuleAnnotation 内に記述していた、依存関係が@Componentに移譲された認識ですので、考え方は同じはずです。

Dagger は画面(or 機能)単位の依存関係を記述するべき

例えばログイン画面、リスト画面、詳細画面の場合の記述

:umbrella: Bad Pattern

[root: ApplicationModule] >[injects: Application.java]
 └──<[includes: AwesomeModule] 
     └──>[injects: LoginActivity.java]
     └──>[injects: ListActivity.java]
     └──>[injects: DetailActivity.java]
@Module(
  includes = AwesomeModule.class,
  library = true
)
public class ApplicationModule {
  ...
}
@Module(
  injects = {
    LoginActivity.class,
    ListActivity.class,
    DetailActivity.class
  },
  library = true
)
public class AwesomeModule {
  ...
}

これでは子の依存関係を作成した場合に不要な @Inject を行ってしまいます。

:sunny: Good Pattern

Dagger graph

See : http://frogermcs.github.io/dagger-1-to-2-migration/

[root: ApplicationModule] >[injects: Application.java]
 ├──<[addsTo: LoginModule] 
 │   └──>[injects: LoginActivity.java]
 ├──<[addsTo: ListModule]
 │   └──>[injects: ListActivity.java]
 └──<[addsTo: DetailModule]
     └──>[injects: DetailActivity.java]
@Module(
  includes = AwesomeModule.class,
  library = true
)
public class ApplicationModule {
  ...
}
@Module(
  injects = LoginActivity.class,         
  addsTo = ApplicationModule.class,
  library = true
)
public class LoginModule {
  ...
}
@Module(
  injects = ListActivity.class,         
  addsTo = ApplicationModule.class,
  library = true
)
public class ListModule {
  ...
}
@Module(
  injects = DetailActivity.class,         
  addsTo = ApplicationModule.class,
  library = true
)
public class DetailModule {
  ...
}

このように addsTo で子の依存関係として追加することで画面(or 機能)毎の注入を選択できるようになります。

Dagger は @Inject 指定された場合、インスタンス化が可能なものはインスタンス化する事を知っておくべき

例えば以下の様な構成の場合

// Retrofit interface
interface YourService {
  getVideo(int id);
  getUser(int id);
}
public class final VideoDao {
  final YourService mService;

  public VideoDao(YourService service) { mService = service; }

  public Observable<VideoModel> get(int id) {
    return service.getVideo(id).first();
  }
}

public class final UserDao {
  final YourService mService;

  public UserDao(YourService service) { mService = service; }

  public Observable<VideoModel> get(int id) {
    return service.getUser(id).first();
  }
}
@Module(complete = false, library = true)
public class ApiModule.java {
  @Provides @Singleton
  public VideoDao provideYourService(RestAdapter adapter) { ... }
}
public class AwesomeActivity extends Activity {
  @Inject
  VideoDao mVideoDao;

  @Inject
  UserDao mUserDao;

  ...
}

:umbrella: Bad Pattern

@Module(
  injects = AwesomeActivity.class,
  includes = ApiModule.class
)
public class DaoModule {
  @Provides @Singleton
  public VideoDao provideVideoDao(YourService service) { ... }

  @Provides @Singleton
  public VideoDao provideUserDao(YourService service) { ... }
}

この場合 YourService@Provides されていれば DAO を@Provides する必要ありません。

:sunny: Good Pattern

+ @Singleton
public class final VideoDao {
  final YourService mService;

+ @Inject
  public VideoDao(YourService service) { mService = service; }

  public Observable<VideoModel> get(int id) {
    return service.getVideo(id).first();
  }
}

+ @Singleton
public class final UserDao {
  final YourService mService;

+ @Inject
  public UserDao(YourService service) { mService = service; }

  public Observable<UserModel> get(int id) {
    return service.getUser(id).first();
  }
}
// Application Module
@Module(
  includes = ApiModule.class,
  injects = YourApplication.class
)
public class ApplicationModule {
  ...
}
// Awesome domain Module
@Module(
  injects = AwesomeActivity.class,
  addsTo = ApplicationModule.class,
  library = true
)
public class AwesomeModule {
}

上記のように Annotation と依存関係を追加すれば、DAO を @Provides する必要がありません。

Dagger で複数インスタンス作成される場合は Lazy<T> を利用すべき

Injection の種類

  • SINGLETON
    • 唯一のインスタンスです。多くの他のオブジェクトから参照されます
  • LAZY
    • 一度生成されると破棄されるまで利用されるインスタンス。
  • PROVIDER
    • 都度生成されるインスタンス。

Provider<T> は便利ですが、自分でインスタンスの管理を行わなければならず混乱を招きます。
これが必要な場合、設計の見直しが必要かもしれません。

:umbrella: Bad Pattern

class MainPresenter {
  @Inject Provider<ChildPresenter> mChildProvider;

  private final List<ChildPresenter> mChilds = new ArrayList<>();

  public void OnViewInflated() {
    // Has 5 childs
    for (int i=0; i < childCount; i++) {
      final ChildPresenter presenter = mChildProvider.get();
      ((ChildView) getView().getChildAt(i)).setPresenter(presenter);
      mChilds.add(presenter);
    }    
  }

  public void fireChildEvent(int id) {
     mChilds.get(id).fire();
  }
}

class ChildPresenter {
  public void fire() { ... }
}
class ChildView extend View {
  private ChildPresenter mPresenter;

  public void setPresenter(ChildPresenter presenter) { mPresenter = presenter; }
}

複数のネストした View が存在する場合は Provider<T> でインスタンスを作成するべきではありません。

:sunny: Good Pattern

class MainPresenter { 
  public void fireChildEvent(int id) {
     ((ChildView) getView().getChildAt(id)).getPresenter().fire();
  }
}

class ChildPresenter {
  public void fire() { ... }
}
class ChildView extend View {
  @Inject
  Lazy<ChildPresenter> mPresenter;

  public ChildPresenter getPresenter() { return mPresenter.get(); }
}

Lazy<T> を使うことで、インスタンスの管理から解放されます。

Dagger でネストした CustomView へ ObjectGraph を渡すときは LayoutInflator.Factory を使うべき

Fragment 等から CustomView へ依存関係を引き渡す必要がある場合があると思いますが、その場合は Factory を作りましょう。
Jake もいってる https://github.com/square/dagger/issues/382
Mortar を使えば考えなくていいんですがね…。

:umbrella: Bad Pattern

public class AwesomeFragment extends Fragment {
  final ObjectGraph mObjectGraph;

  @Override
  public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mObjectGraph = MainApplication.get(getActivity()).getObjectGraph()
          .plus(getDaggerModule());
        mObjectGraph.inject(this);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    final View view = inflater.inflate(R.layout.awesome_fragment,
      container, false);
    mObjectGraph.inject(view.findViewById(R.layout.awesome_view));
    return view;
  }

  ...
}

これだと Fragment と CustomView が ObjectGraph の状態によって密結合してしまうので、避けたほうがいいでしょう。

:sunny: Good Pattern

public class AwesomeView extends FrameLayout {
  static public class Factory implements LayoutInflater.Factory {
    final ObjectGraph mObjectGraph;

    public Factory(ObjectGraph objectGraph) { mObjectGraph = objectGraph; }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
    // null を返すと標準のファクトリを使うので、型名で判定する。
    if (!name.equalsIgnoreCase(AwesomeView.class.getName())) {
      return null;
    }

    return new AwesomeView(context, attrs, mObjectGraph);
  }

  public AwesomeView(Context context, AttributeSet attrs, ObjectGraph objectGraph) {
    objectGraph.inject(this);
  }

  ...
}
public class AwesomeFragment extends Fragment {
  final ObjectGraph mObjectGraph;

  @Override
  public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mObjectGraph = MainApplication.get(getActivity()).getObjectGraph()
          .plus(getDaggerModule());
        mObjectGraph.inject(this);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    final LayoutInflater daggerInflater = inflater.cloneInContext(getActivity);
    daggerInflater.setFactory(new AwesomeView.Factory(mObjectGraph));

    final View view = inflater.inflate(R.layout.awesome_fragment,
      container, false);
    return view;
  }

  ...
}

CustomView 内で inject 操作を行ったほうが関係性を疎にできます。

参考

https://github.com/square/dagger
https://github.com/JakeWharton/u2020
https://github.com/LiveTyping/u2020-mvp

146
145
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
146
145