LiveData-ktxでコルーチンのキャンセル処理を自動化しよう
Lifecycle-aware components + Kotiln Coroutines
コルーチンを使う際に必要なキャンセル処理を、Lifecycle-aware componentsの一部であるLiveData-KTXを使って実装します。明示的にキャンセル処理を実装する必要が無くなります。
準備
dependencies { def lifecycle_version = "2.2.0-alpha02" ... implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" }
UseCase
UseCaseクラスはビジネスロジックの実装をします。
abstract class UseCase<in P, R> { private var currentLiveData: LiveData<State<R>>? = null operator fun invoke(parameters: P, mediator: MediatorLiveData<State<R>>) { currentLiveData?.let { mediator.removeSource(it) } // ktx val ld = liveData { emit(State.Loading) try { emit(State.Success(withContext(Dispatchers.IO) { execute(parameters) })) } catch (e: Exception) { Timber.w(e) emit(State.Failure(e)) } } mediator.addSource(ld) { mediator.value = it } currentLiveData = ld } abstract suspend fun execute(parameters: P): R } class GetTodoListUseCase( private val todoRepository: TodoRepository ) : UseCase<Unit, List<Todo>>() { override suspend fun execute(parameters: Unit): List<Todo> { // ビジネスロジックを記載する(ex. 取得したリストの1~3番目だけ加工する...etc) return todoRepository.getNotDoneTodoList() } }
liveData {}
が肝となる箇所です。
これで生成するLiveDataはinactive時にキャンセル処理を行ってくれます。また、値の通知にはemit(value)
を利用します。※Stateはローディング中・処理完了・処理失敗の状態管理クラスです。
また、UseCaseは再利用(parametersを変えて再リクエスト)可能にしたいので、リクエスト時にMediatorLiveData
を渡し、先程生成したLiveDataを追加しています。(既にリクエストがあった場合は破棄してから追加)
ViewModel
続いてViewModelの実装です。受け取ったリストをLiveDataにセットします。(表示はViewに任せましょう)
class TodoListViewModel( getTodoListUseCase: GetTodoListUseCase ) : ViewModel() { private val _todoList = MediatorLiveData<State<List<Todo>>>() val infoType = MediatorLiveData<InfoType>() val todoList = MediatorLiveData<List<Todo>>() init { infoType.addSource(_todoList) { state -> infoType.value = when (state) { is State.Loading -> InfoType.Progress else -> InfoType.Transparent } } todoList.addSource(_todoList) { state -> (state as? State.Success)?.result?.let { todoList.value = it } } getTodoListUseCase(Unit, _todoList) } }
_todoList
はUseCaseの実行結果を受け取るためのLiveDataです。
todoList
は表示用のLiveDataですが、_todoList
の変更を監視したいので、MediatorLiveData
としています。
これまではViewModel.onCleared()
にて、UseCase等のコルーチンをキャンセルする必要がありましたが、LiveDataが管理しているのでViewModelでの記載は不要となります。
おわりに
ViewModelにて、liveData {} を利用して直接データ操作する手法も散見しますが、UseCaseに分けることでボイラープレートが減ると思います。お試しください。
※筆者はViewModelでemitしたり、showLoading()/hideLoading()したくない派です