@Binds - Dagger2

インターフェースを実装したクラスがあり、それをインターフェース経由でバインドしたいとき、通常以下のようにしてました。


@Module
object BookPresenterModule {
  @Provides @JvmStatic
  fun provideBookPresenter(bookPresenter: BookPresenterImpl): BookPresenter = bookPresenter
}

これは @Inject 付きコンストラクタと一緒に使われるモジュールの一部です。これは、以下のように記述するべきです。


@Module
abstract class BookPresenterModule {
  @Binds abstract fun bindBookPresenter(bookPresenter: BookPresenterImpl): BookPresenter
}

これまでどおり「インターフェースにその実装をバインドしたいとき」に使うことができます。

コード生成がされず、どこからもコールされないのに、きちんと連携情報として利用されるのが良いところです。

Dagger 2.4 で登場したにもかかわらず、なぜか公式ユーザーガイドでは説明されていません。

👉 Release Dagger 2.4 · google/dagger 

以下、公演動画などから学ぶことができます。




ApplicationComponent 実装の変遷 - Dagger2

2022-03-16 追記: 新しいDagger記事は以下リンクから

👉 MVVM で Hilt のパターン化 💉  

--------

へん‐せん【変遷】
[名](スル)時の流れとともに移り変わること。「歌もまた時代につれて変遷する」

Dagger て分かりづらいです。

タイトルがすでに謎ですが、以下のような実装のことを指しています。

アプリケーションコンテキストをオブジェクトグラフに追加する

ApplicationComponent とアプリケーションコンテキストの設定

アプリケーションコンテキスト を依存先として公開する

これまで数年に渡って変化し続けてるそんな必須の実装項目です。

ネット上をただ検索するだけでは、古い記事に最新の実装記述が埋没しています。

Dagger 2.9 以前( - 2017/02/04)


@Component(modules = [ApplicationModule::class, ...])
interface ApplicationComponent {
  ...
}


@Module
class ApplicationModule(private val applicationContext: Context) {
  @Provides fun provideApplicationContext() = applicationContext
}

これは Kotlin 記述で簡略化できます。


@Module
class ApplicationModule(@get:Provides val applicationContext: Context)


DaggerApplicationComponent
  .builder()
  .applicationModule(ApplicationModule(applicationContext))
  .build()

2.9 (2017/02/04 - ) @BindsInstance

👉 Release Dagger 2.9 · google/dagger 

We create a module that receives the application context as a constructor argument, and we create a provide method that exposes it. This works great, but then we can't have static @Provides methods anymore. And besides that, this strategy is actually going against the docs that are pretty explicit when it comes to this:

@BindsInstance methods should be preferred to writing a @Module with constructor arguments and immediately providing those values.

👉 User's Guide 


@Component(modules = ...)
interface ApplicationComponent {
  @Component.Builder
  interface Builder {
    @BindsInstance 
    fun applicationContext(applicationContext: Context): Builder
    fun build(): ApplicationComponent
  }
  ...
}


DaggerApplicationComponent
  .builder()
  .applicationContext(applicationContext)
  .build()

2.22 (2019/04/03 - ) @Component.Factory

👉 Release Dagger 2.22 · google/dagger 


@Component(modules = ...)
interface ApplicationComponent {
  @Component.Factory
  interface Factory {
    fun create(@BindsInstance applicationContext: Context): ApplicationComponent
  }
  ...
}


DaggerApplicationComponent
  .factory()
  .create(applicationContext)

まとめ

最近のコード記述を理解するには、少しだけ

「成り立ちを遡ってみる」

と理解しやすいことが多いように思います。

👉 Releases · google/dagger 
👉 Dagger 2 on Android: the shiny new @Component.Factory 

2022-03-16 追記: 新しいDagger記事は以下リンクから

👉 MVVM で Hilt のパターン化 💉  


さらば proguard、ようこそ R8。

DSL element 'useProguard' is obsolete and will be removed soon. Use 'android.enableR8' in gradle.properties to switch between R8 and Proguard..

DSL要素「useProguard」は廃止されており、まもなく削除されます。 gradle.propertiesで「android.enableR8」を使用して、R8とProguardを切り替えます。

gradle.properties


android.enableR8=true

build.gradle

とりあえずは、いけるが。

以下、編集風景。



しかし、minify とか、proguardFiles とか、どうなるの?

👉 Android Developers Blog: R8, the new code shrinker from Google, is available in Android studio 3.3 beta 


Studio does not have write access to /private/var/folders/Android Studio.app/Contents. Please run it by a privileged user to update. で Android Studio が更新できない。

これ。

Studio does not have write access to /private/var/folders/Android Studio.app/Contents. Please run it by a privileged user to update.

と表示されて、新バージョンに更新できません。

Toolbox App のトッラカーにて。

👉 Android 3.5.0 not available many hours after release : TBX-3894 

Toolbox App 公式ブログ。


👉 JetBrains Toolbox 2019.2 | JetBrains Blog 

こういうところで、Google と JetBrains 間で微妙にズレちゃうんでしょう。

今現在私のMac版は最新で。

Toolbox App 側が更新されるのをじっと待つのが吉でしょうな。

追記:2019-08-21 20:07

Toolbox App のバージョンはそのままだけども、Android Studio 3.4 の「UPDATE」ボタンが有効になって3.5へのアップデートが可能となりました!

そして、何度かのダイアログ「OK」→「Restart」を経てやっとアップデート完了できましたとさ。

👉 さらば proguard、ようこそ R8。 

(👉つづく...)


【Dagger】@Provides vs @Binds

久々に Dagger を使うとかなりの不自由感。

細かく咀嚼しないと全体は理解できない感じなので、まずはこのタイトルから。

少し、細かくシリーズ化してみますが。

👉 Dagger 2 @Binds vs @Provides - Elye - Medium 

👉 Why is @Binds different from @Provides? - Frequently Asked Questions 

Android が登場してから10年程度ですが、経緯というか、伝統というか、歴史というか。

そういうの嫌いだけど、そういうのを理解しようとしないと、その対象に対しての初心者は辛い時期になってるのだろうと思う。

Toolbar / ActionBar もそうでしょう?

シンプルにググるだけでは良いコードには辿り着けません、

この、Dagger2 2019-08-15現在 もそれと同じ雰囲気なのでしょう。

以下、上記リンクより引用。

@Binds methods must have only one parameter whose type is assignable to the return type

So only a single parameter, and the type return is typically the interface of the given parameter object.

Having said that, the other tips is consider using static function for @Provides which would help also reduce some generated codes.

@Bindsメソッドには、戻り値の型に割り当て可能な型のパラメーターが1つだけ必要です

そのため、1つのパラメーターのみが返され、型の戻り値は通常、指定されたパラメーターオブジェクトのインターフェイスです。

そうは言っても、他のヒントは、生成されるコードを減らすのに役立つ「@Provides」の「静的」関数の使用を検討することです。

@Provides, the most common construct for configuring a binding, serves three functions:
1. Declare which type (possibly qualified) is being provided — this is the return type
2. Declare dependencies — these are the method parameters
3. Provide an implementation for exactly how the instance is provided — this is the method body

While the first two functions are unique and critical to every @Provides method, the third can often be tedious and repetitive. So, whenever there is a @Provides whose implementation is simple and common enough to be inferred by Dagger, it makes sense to just declare that as a method without a body (an abstract method) and have Dagger apply the behavior.

But, if we were to just say that abstract @Provides methods should be treated as we do for @Binds methods, the specification of @Provides would basically be two specifications with a bunch of conditional logic. For example, a @Provides method can have any number of parameters of any type, but a @Binds method can only have a single parameter whose type is assignable to the return type. Separating those specifications makes it easier to reason about correctness because the annotation determines the constraints.

バインディングを構成するための最も一般的な構成要素である@Providesは、3つの機能を提供します。
 1.どのタイプ(修飾されている可能性がある)が提供されているかを宣言します—これは戻りタイプです
 2.依存関係を宣言します—これらはメソッドのパラメーターです
 3.インスタンスが提供される方法を正確に実装します—これはメソッド本体です

最初の2つの関数は一意であり、「すべての@Provides」メソッドにとって重要ですが、3番目の関数は多くの場合、退屈で反復的です。そのため、実装がDaggerによって推論されるほど単純で一般的な@Providesが存在する場合はいつでも、それを本体のないメソッド(抽象メソッド)として宣言し、Daggerに動作を適用させることが理にかなっています。

しかし、abstract @Provides methodsを@Binds methodsのように扱うべきであると言うだけなら、@Providesの仕様は基本的に条件付きロジックの束を持つ2つの仕様になります。たとえば、@Provides methodは、任意の型の任意の数のパラメーターを持つことができますが、a @Binds methodは、型が戻り型に割り当て可能な単一のパラメーターのみを持つことができます。これらの仕様を分離すると、注釈によって制約が決定されるため、正確性についての推論が容易になります。

まとめ

kotlin でいうとこの

1. 基本 @Binds を使う。
2. Application 起動の object の場合(static な singleton メソッドの場合)は @Provides を使う。

という理解でいいのか。

👉 ATM - Dagger Tutorial