Androidアプリ開発 DI Kodeinのfactory関数を実装する

2018年03月11日(編集2018年03月14日)
このエントリーをはてなブックマークに追加

Androidアプリ開発では、springフレームワークを使った開発のように、DIコンテナを利用できます。AndroidでJavaを使った開発ではDagger 2がよく利用されていますが、kotlinではKodeinを使うのが一般的です。

この記事は、Kodeinを使ってDIを実装する方法を記載した記事です。

環境はAndroid 8.1 (API level 27) です。

環境

  • macOS Sierra
  • android sdk 27
  • Oracle jdk version 1.8.0_72
  • Android Studio 3.0.1
  • kotlin 1.2.30

難易度

中級者向け

サンプルコード

Android-kotlin-Library-Demo

DI

DIの定義を一言で表現すると、「外からオブジェクトを設定する」です。
DIを使うと疎結合なコードを書けるようになり、テストコードの実装やコードの修正が容易になります。

アプリ設計

サンプルアプリを作成してKodeinを使用します。
画面のボタンを押して、Kodeinのfactory関数を実行します。

インストール

Kodeinを使うには、{project_folder}/app/build.gradleに下記のコードを記述します。

{project_folder}/app/build.gradle
dependencies {
  implementation 'com.github.salomonbrys.kodein:kodein:4.1.0'
  implementation 'com.github.salomonbrys.kodein:kodein-android:4.1.0'
}
        

当然ですが、プロジェクトでkotlinを導入している必要があります。

インストールが完了すれば準備は完了です。

factory実装

Activityでfactory関数を使った実装をします。

{project_folder}/app/src/main/KodeinActivity.kt
class KodeinActivity : AppCompatActivity() {

    // 1. Kodeinの宣言
    private val helloFactory = Kodein {
        // 2. Factory binding
        bind<String>() with factory { type: Int -> Sample.calling(type) }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_kodein)

        btn_factory.setOnClickListener(onClickFactoryListener)
    }

    private val onClickFactoryListener = View.OnClickListener {
        // 3. Factory関数呼び出し
        result.text = helloFactory.factory<Int, String>().invoke(0)
    }
}
      

オブジェクトで関数を実装します。

{project_folder}/app/src/main/Sample.kt
object Sample {
    fun calling(type: Int): String {
        return when (type) {
            1 -> "called type is $type"
            2 -> "called type is $type"
            else -> "called type is $type"
        }
    }
}
      

■ 実装の解説

1. Kodeinの宣言

Kodeinクラスとブロックを実装します。

Kodein {

}

Kodeinブロックの中に関数を実装します。これがKodeinのDIの形です。

2. Factory binding

factory関数はKodeinブロック内で実装します。

bind<String>() with factory { type: Int -> Sample.calling(type) }

上記はString型をfactory関数にbindしています。
bind関数は、指定された型(上記はString)のbindを開始します。

より理解を深めるために、Kodeinのbind関数のコードを読んでいきましょう。

// com.github.salomonbrys.kodein
inline fun <reified T : Any> Kodein.Builder.bind(tag: Any? = null, overrides: Boolean? = null) = Bind<T>(generic(), tag, overrides)

bind関数は拡張関数で実装されています。そして、ジェネリッククラスにreified type paramterを使っています。

■ 拡張関数とReified type parameters

Kodeinで使われている拡張関数Reified type parametersは、普段の実装では利用しない人のほうが多いのではないでしょうか。実際、知らなくてもkotlinを使った実装は可能です。
特に、Reified type parametersは公式ドキュメントを読むだけでは理解は難しいです。なので、なおさら理解している人は少ないです。

kotlinの名著『Kotlinイン・アクション』(書影をクリックするとアマゾンのサイトにジャンプします)

しかし、拡張関数Reified type parametersは、拡張性の高いコードを書いたり、ライブラリのコードを読むのに役立つ技術です。特に、ライブラリのコードを読む場合に重宝します。コードを読めればライブラリのバグや仕様の理解をコードから判断できるので、習得しておくことをオススメします。

習得の方法ですが、公式ドキュメントは情報の質は高いのですが、理解が難しいので「Kotlinイン・アクション」を利用することをオススメします。公式ドキュメントよりも、ページを多く割いて丁寧に説明しています。

今回のサンプルは「bind<String>()」なので、引数tagとoverridesはnullです。
Bind関数は、TypeBinderクラスを生成します。TypeBinderクラスはwith関数を宣言しています。

// com.github.salomonbrys.kodein
class TypeBinder<T : Any> internal constructor(internal val binder: KodeinContainer.Builder.BindBinder<T>) {
  infix fun with(binding: Binding<*, out T>) = binder with binding
}

with関数はinfixキーワードが宣言されているので、中置表記法を使用して関数を呼び出せます。

bind<String>() with

with関数の引数はBinding型です。factory関数はここで引き渡します。

bind<String>() with factory

factory関数は、Bindingインターフェースを継承し、FactoryBindingクラスを生成します。

// com.github.salomonbrys.kodein.bindings
class FactoryBinding<A, T: Any>(override val argType: TypeToken<in A>, override val createdType: TypeToken<out T>, val creator: BindingKodein.(A) -> T) : Binding<A, T> {
    override fun factoryName() = "factory"

    override fun getInstance(kodein: BindingKodein, key: Kodein.Key<A, T>, arg: A) = this.creator(kodein, arg)

}

factory関数の引数に渡す関数は、引数をとり、Bindのオブジェクトを返します。
引数を取るのは、factory関数の引数がBindingKodeinインターフェースだからです。

// com.github.salomonbrys.kodein
inline fun <reified A, reified T: Any> Kodein.Builder.factory(noinline creator: BindingKodein.(A) -> T) = FactoryBinding<A, T>(generic(), generic(), creator)

記事のサンプルでは「Int型を引数に取りString型を返す関数」をブロックに渡します。

bind<String>() with factory { type: Int -> MyString(type).msg() }

引数に渡された関数は、バインドされた型のインスタンスが必要になるたびに毎回呼び出されます。

3. Factory関数呼び出し

factory関数で実装した処理は、Kodeinオブジェクト.factory<引数の型, 返却の型>.invoke(引数の型)で呼び出せます。

helloFactory.factory<Int, String>().invoke(1)

invoke関数でSample.calling(1)を呼び出します。

invoke関数はKodeinインターフェースで実装されています。

// com.github.salomonbrys.kodein
interface Kodein : KodeinAwareBase {
    companion object {

        /**
        * Creates a [Kodein] instance.
        *
        * @param allowSilentOverride Whether the configuration block is allowed to non-explicit overrides.
        * @param init The block of configuration.
        * @return The new Kodein object, freshly created, and ready for hard work!
        */
        operator fun invoke(allowSilentOverride: Boolean = false, init: Kodein.Builder.() -> Unit): Kodein = KodeinImpl(allowSilentOverride, init)
    }
}

ここでは省力しますが、余裕が出てきたらinvoke関数が実行されるプロセスを追ってみると良いと思います。

ビルド

上記コードの動作を確認しましょう。factoryボタンを押してhelloFactoryを実行します。

fig1. factory関数の実行結果

うまく動作するのが確認できました。

結論

androidアプリは、今後ますます肥大化が予想されます。なので、疎結合な実装ができるDIを使う機会は増えると思います。その際にKodeinは役に立つので是非マスターしてください。

また、kotlinの実装に慣れてきた人は、Kodeinのソースコードを研究してみてください。kotlinの実力を飛躍的にアップできると思います。

タグ検索で調べてみよう

kotlin1.2 Android8.1