viewmodel-ktx 2.1.0で導入されるviewModelScopeを使えばCoroutineScopeを実装しなくてよくなる

`androidx.lifecycle:lifecycle-viewmodel-ktx` の 2.1.0 (2019年1月1日時点でまだ `alpha01` ですが) から、`ViewModel` に `viewModelScope` という `CoroutineScope` 型の拡張プロパティが導入されます。これを使うと、ViewModel において `CoroutineScope` を実装[^1]しなくても、`ViewModel.onCleared()` のタイミングでキャンセルされる `CoroutineScope` が利用できるようになります。

```kotlin
// 2.1.0 より前
class MyViewModel : ViewModel(), CoroutineScope {
    private val job = Job()
    override val coroutineContext = Dispatchers.Main + job

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }

    fun doSomething() {
        launch {
            // ...
        }
    }
}
```

```kotlin
// 2.1.0 以降
class MyViewModel : ViewModel() {
    fun doSomething() {
        viewModelScope.launch {
            // ...
        }
    }
}
```

# 仕組み

`viewModelScope` の内容は `Job() + Dispatchers.Main` です。 `viewModelScope` プロパティへの初回アクセス時にインスタンスが作成され、`ViewModel` のタグとして保持されます ([ソースコード](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt))。

```kotlin:lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(Job() + Dispatchers.Main))
        }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
    }
}
```

タグは、任意の型のオブジェクトを `String` 型のキーに紐づけて `ViewModel` に登録するための仕組みで、`lifecycle-viewmodel` の 2.1.0 で `ViewModel` に導入されます。タグが `Closeable` を実装していると `ViewModel.onCleared()` の直前にタグの `close()` が呼ばれるようなっており、`viewModelScope` は、それを利用して、`ViewModel` のクリア時に `CoroutineContext` をキャンセルしています。以下に、[`ViewModel` の関連する実装](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModel.java)を抜粋します。

```java:lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModel.java
public abstract class ViewModel {
    @Nullable
    private final ConcurrentHashMap<String, Object> mBagOfTags = new ConcurrentHashMap<>();

    // 中略...

    final void clear() {
        mCleared = true;
        for (Object value: mBagOfTags.values()) {
            // see comment for the similar call in setTagIfAbsent
            closeWithRuntimeException(value);
        }
        onCleared();
    }

    // 中略...

    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
```

タグを使えば、後処理が必要となるような `ViewModel` の共通処理をBaseクラスを作ったりせずに実装できるので、他の目的でも便利に使えそうです。

## 2019年1月3日追記

便利に使えそう、と思ったのですが、 タグを操作するメソッド `getTag()` と `setTagIfAbsent()` のアクセス修飾子が `public` や `protected` ではなく、default (package-private) でした... `androidx.lifecycle` パッケージにコードを書けば無理やりアクセスできるかもしれませんが、タグは、アプリから使われることを想定していない機能のようです 😢

[^1]: `ViewModel` で `CoroutineScope` を用意する方法として、`ViewModel` が `CoroutineScope` を実装する以外にも、[GoogleのKotlin CoroutinesのCodelab](https://codelabs.developers.google.com/codelabs/kotlin-coroutines/index.html#4) にあるように `CoroutineScope` 型のインスタンスを生成してプロパティに保持しておくこともできます (こちらのほうが望ましいかもしれません)