41
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-01

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

// 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 {
            // ...
        }
    }
}
// 2.1.0 以降
class MyViewModel : ViewModel() {
    fun doSomething() {
        viewModelScope.launch {
            // ...
        }
    }
}

仕組み

viewModelScope の内容は Job() + Dispatchers.Main です。 viewModelScope プロパティへの初回アクセス時にインスタンスが作成され、ViewModel のタグとして保持されます (ソースコード)。

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 の関連する実装を抜粋します。

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() のアクセス修飾子が publicprotected ではなく、default (package-private) でした... androidx.lifecycle パッケージにコードを書けば無理やりアクセスできるかもしれませんが、タグは、アプリから使われることを想定していない機能のようです 😢

  1. ViewModelCoroutineScope を用意する方法として、ViewModelCoroutineScope を実装する以外にも、GoogleのKotlin CoroutinesのCodelab にあるように CoroutineScope 型のインスタンスを生成してプロパティに保持しておくこともできます (こちらのほうが望ましいかもしれません)

41
25
0

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
41
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?