初心者が流し読みで理解できるAndroidアプリのDI（勉強会資料）

# DIとは
**Dependency Injection**の略。日本語で「依存性の注入」

コンポーネント間の依存関係を外部から注入するデザインパターン（と言う一文は理解できなくてOK）

---

## DIしたいアプリの例
`UrlProvider`は、通信先URLを生成するクラス
![DI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/8c128d42-7f46-4ea7-9b05-450e5840ad98.png)
3つのクラスが`UrlProvider`を使っている

1. `WebView` URLをアプリ内部で表示する
1. `OpenBrowser` URLをアプリ外部のブラウザで表示する
1. `HttpClient` URLとバックグラウンドで通信する

---

### 実装はこんな感じ（関係ない部分は省略）

```kotlin
class WebView {
    private val urlProvider = UrlProvider()
    ・・・
}
class OpenBrowser {
    private val urlProvider = UrlProvider()
    ・・・
}
class HttpClient {
    private val urlProvider = UrlProvider()
    ・・・
}
```

---

### 懸念

`UrlProvider`を別々に初期化するので、

1. メモリを2倍、3倍食う
2. どれかが変更されたら、他2箇所と内容が変わる
3. UrlProviderが完成するまで、動作確認もテストも出来ない

---

### 改善すると、こうなる

```kotlin
class WebView(urlProvider: UrlProvider) { ・・・ }
class OpenBrowser(urlProvider: UrlProvider) { ・・・ }
class HttpClient(urlProvider: UrlProvider) { ・・・ }

class MainActivity {
    private val urlProvider = UrlProvider()
    // 完成までは以下を使う
    // private val urlProvider = MockUrlProvider()
    override fun onCreate(savedInstanceState: Bundle?) {
        val webView = WebView(urlProvider)
        val openBrowser = OpenBrowser(urlProvider)
        val httpClient = HttpClient(urlProvider)
        ・・・
    }
}
```

先述の一文に則れば、一応この時点でDIできたと言える

> [NOTE]オブジェクト生成時に外部から必要なものを注入しているから、DIの条件を満たす

---

### でも、アプリの開発が進んでこうなったら？

![DI2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/362822c7-2c4e-d7b6-3e46-47a377c6606e.png)

---

### 悪夢！！

![DI3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/fe2291e8-f8f8-bc51-b41d-3c38d54abccd.png)

---

### その時の実装はこんな感じ（めっちゃ省略）

```kotlin
class LoginFragment(urlProvider: UrlProvider) { ・・・ }
class WebViewFragment(urlProvider: UrlProvider) { ・・・ }
class HomeFragment(urlProvider: UrlProvider) { ・・・ }
class LoginViewModel(urlProvider: UrlProvider) { ・・・ }
class FaqViewModel(urlProvider: UrlProvider) { ・・・ }
class BannerViewModel(urlProvider: UrlProvider) { ・・・ }
class Login(urlProvider: UrlProvider) { ・・・ }
class LoginRepository(urlProvider: UrlProvider) { ・・・ }
class LoginService(urlProvider: UrlProvider) { ・・・ }
```

---

### ダサいのは一目瞭然だが、具体的にどうダメなのか

`UrlProvider`の引数を変えたり、`UrlProvider`自体を別のものに変えた時、ほぼ全部のクラスを直さなきゃならなくなる

---

### そこでDIツール登場！

![DI3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/a15b46a6-57fa-0c3f-ed27-0ffe19e11c4b.png)

---

### あっ、`UrlProvider`は全クラスに渡さなくてもいいね！（うっかり）

![DI4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/ba5e8ba9-2671-6090-661c-d5da59969054.png)

これが最小限のDI実装

---

## DIツールの役割（重要!!）

1. **インスタンスを1個作る**（UrlProvider）
2. **作ったインスタンスを必要としているクラスに配布する**（To: FaqViewModel, BannerViewModel, LoginService）
3. **引数を使わず初期化できるようサポートする**(To: HomeFragment, WebViewFragment, LoginRepository)

ちなみに1は、1個と言わず複数作ることも可能（Singletonパターン/Factoryパターンを選べる）

---

# DIツール適用手順
## 1. DIツールの設定（前頁の役割1,2）

Koinの例

```kotlin
class KoinApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            modules(module)
        }
    }
    val module = module {
        single { UrlProvider() } // インスタンス作成
        single { LoginService(get()) } // 1つ目のクラスにインスタンス注入
        viewModel { FaqViewModel(get()) } // 2つ目のクラスにインスタンス注入
        viewModel { BannerViewModel(get()) } // 3つ目のクラスにインスタンス注入
    }
}
```

ちなみにインターフェースと実体がある場合はこう書く

```kotlin
single { UrlProviderImpl() as UrlProvider }  
```

---

## 2. 各クラスでの初期化処理（前々頁の役割3）

```kotlin
class LoginRepository() {
    private val loginService: LoginService by inject() // DIツールからインスタンスを受け取る
    ・・・
}

class WebViewFragment : Fragment() {
    private val faqViewModel: FaqViewModel by viewModel() // DIツールからインスタンスを受け取る
    ・・・
}

class HomeFragment : Fragment() {
    private val bannerViewModel: BannerViewModel by viewModel() // DIツールからインスタンスを受け取る
    ・・・
}
```

このように、DIツールのサポートを受けて初期化する

---

## 以上！

DI初心者が流し読みしても理解できそうなテンポで書いたつもりですが、おわかりいただけたでしょうか。

夜中に眠れず勢いで書いた記事なので、ここがわかりづらい！とか間違ってる！とかあればコメントください。

---

# 最後にDIのメリット/デメリット

* メリット
  * クラス同士の依存が減り、クラスの差し替えが容易になる
  * インスタンスをあちこちで使いまわせる
  * 通信処理をダミーに差し替えるなどすれば、単体テストが容易になる
  * [クリーンアーキテクチャー](https://qiita.com/knagauchi/items/8c9fb93520b56fd3a564)を構築できる
* デメリット
  * 最初から全体を考えて複数クラスを同時実装するので、初期開発コストがかかる
  * 学習コストがかかる
  * 処理速度が多少伸びる
  * DIツール自体を差し替えるのが難しい

---

# 最後に

実際にDIを使ったサンプルアプリ[UnusedAppFinder](https://github.com/tonionagauzzi/UnusedAppFinder)もあるので、~~半年間メンテを怠っていますが~~参考にして下さい。

---

# 余談：DIを適用した車！？

https://copen.jp/product/dress