androidx.lifecycle:lifecycle-viewmodel-ktx
の 2.1.0 (2019年1月1日時点でまだ alpha01
ですが) から、ViewModel
に viewModelScope
という 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
のタグとして保持されます (ソースコード)。
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
の関連する実装を抜粋します。
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
パッケージにコードを書けば無理やりアクセスできるかもしれませんが、タグは、アプリから使われることを想定していない機能のようです 😢
-
ViewModel
でCoroutineScope
を用意する方法として、ViewModel
がCoroutineScope
を実装する以外にも、GoogleのKotlin CoroutinesのCodelab にあるようにCoroutineScope
型のインスタンスを生成してプロパティに保持しておくこともできます (こちらのほうが望ましいかもしれません) ↩