クローズドな一般国内環境にて、
他人の書いたコードというのは、
非常に読みづらいものです。
Dagger のようなバイトコードを生成するコンパイルタイムなフレームワークであればさらにその特性は増してしまいます。
図で視覚化して概要を捉えましょう。
👉 dvdciri/daggraph: Dagger dependency graph generator for Android Developers
シンプルなコマンドラインツールなので入れてみて損はないでしょう。
クローズドな一般国内環境にて、
他人の書いたコードというのは、
非常に読みづらいものです。
Dagger のようなバイトコードを生成するコンパイルタイムなフレームワークであればさらにその特性は増してしまいます。
図で視覚化して概要を捉えましょう。
👉 dvdciri/daggraph: Dagger dependency graph generator for Android Developers
シンプルなコマンドラインツールなので入れてみて損はないでしょう。
「MVVM」あたりを調べていると「Presentation Model」という言葉がやたら目につくので調べる。
マーチン・ファウラーさんがつぶやいていました。7年前。
@HerberthAmaral MVVM is the Microsoft's world's name for the same pattern as Presentation Model. I haven't updated the article since.
— Martin Fowler (@martinfowler) January 20, 2012
MVVM is the Microsoft's world's name for the same pattern as Presentation Model. I haven't updated the article since.
マイクロソフトの「MVVM」パターンは「Presentation Model」と同じなので、記事は更新していません。
それぞれのソースを探す。
Represent the state and behavior of the presentation independently of the GUI controls used in the interface
Model/View/ViewModel is a variation of Model/View/Controller (MVC) that is tailored for modern UI development platforms where the View is the responsibility of a designer rather than a classic developer. The designer is generally a more graphical, artistic focused person, and does less classic coding than a traditional developer.
👉 Introduction to Model/View/ViewModel pattern for building WPF apps – Tales from the Smart Client
2012年4月現在、XAMLを使用するWPFなどのテクノロジ以外で使用されるMVVMは実質Presentation Modelと変わらず、Viewの抽象化などはできない。
👉 Model View ViewModel - Wikipedia (ja)
MVVM is a variation of Martin Fowler's Presentation Model design pattern. MVVM abstracts a view's state and behavior in the same way, but a Presentation Model abstracts a view (creates a view model) in a manner not dependent on a specific user-interface platform.
👉 Model–view–viewmodel - Wikipedia (en)
同じと思っていいのか。
気がついたら「v2」です。MVVM のみとなって、 RxJava は姿を消していますが。
現在、4つのバリエーションが公開されていますが、以下は共通。
- kotlin
- coroutine
- single activity
- architecture component
- navigation component + fragment
- presentetion layer(per page) = fragment + view model
- reactive ui = live data + data binding
- data layer = repositpory + local(room) + remote
- datalayer one shot operations(no listener or data streams)
非同期処理には、どのバリエーションも「coroutine」使う。
どのように coroutine を使っているか、を定型化しておきたい。
「kotlinx-coroutines-core」の関数の「coroutineScope」 が目についたので見ておく。
suspend fun <R> coroutineScope(
block: suspend CoroutineScope.() → R
): R (source)
CoroutineScope を作成し、このスコープで指定された suspend ブロックを呼び出します。その外側のスコープから coroutineContext を継承しますが、そのコンテキストのJobをオーバーライドします。
この関数は、処理の並列分解用に設計されています。 このスコープ内のいずれかの子コルーチンが失敗すると、このスコープは失敗し、残りのすべての子はキャンセルされます(supervisorScope との違いを参照してください)。 与えられたブロックとそのすべての子コルーチンが完了するとすぐに戻ります。
スコープの使用例は次のようになります。
suspend fun showSomeData() = coroutineScope {
val data = async(Dispatchers.IO) { // <- extension on current scope
// ... load some UI data for the Main thread ...
}
withContext(Dispatchers.Main) {
doSomeWork()
val result = data.await()
display(result)
}
}
この例のスコープの意味は次のとおりです。
1. showSomeDataは、データがロードされてUIに表示されるとすぐに戻ります。
2. doSomeWorkが例外をスローすると、非同期タスクはキャンセルされ、showSomeDataはその例外を再スローします。
3. showSomeData の外部スコープが取り消されると、開始された async ブロックとwithContext ブロックの両方が取り消されます。
4. 非同期ブロックが失敗すると、withContext はキャンセルされます。
現在のジョブが外部でキャンセルされた場合、メソッドは CancellationException をスローします。このスコープ内に未処理の例外がある場合(たとえば、このスコープ内の起動で開始されたクラッシュコルーチンから)は、対応する未処理のThrowableをスローします。
coroutineScope - kotlinx-coroutines-core
coroutine を使った非同期処理は 各 ViewModel を起点に記述されています。
class TasksViewModel(
private val tasksRepository: TasksRepository
) : ViewModel() {
fun loadTasks(forceUpdate: Boolean) {
viewModelScope.launch {
// 対応する Repository メソッドをコール
// val tasksResult = tasksRepository.getTasks(forceUpdate)
}
}
}
👉 android-architecture/TasksViewModel.kt at master · googlesamples/android-architecture
Repository を展開して、スコープ周りを抜き出してみると、
class TasksViewModel(
private val tasksRepository: TasksRepository
) : ViewModel() {
fun loadTasks(forceUpdate: Boolean) {
viewModelScope.launch {
withContext(ioDispatcher) {
// ...
}
}
}
}
というこれまで通り withContext() を使った dispatcher の切り換え。
他には、
前述の coroutineScope 関数を使った記述が、単独、並列、入れ子の3種類。
override suspend fun saveTask(task: Task) {
coroutineScope {
launch { tasksRemoteDataSource.saveTask(it) }
launch { tasksLocalDataSource.saveTask(it) }
}
}
override suspend fun clearCompletedTasks() {
coroutineScope {
launch { tasksRemoteDataSource.clearCompletedTasks() }
launch { tasksLocalDataSource.clearCompletedTasks() }
}
withContext(ioDispatcher) {
cachedTasks?.entries?.removeAll { it.value.isCompleted }
}
}
override suspend fun deleteAllTasks() {
withContext(ioDispatcher) {
coroutineScope {
launch { tasksRemoteDataSource.deleteAllTasks() }
launch { tasksLocalDataSource.deleteAllTasks() }
}
}
}
👉 android-architecture/DefaultTasksRepository.kt at master · googlesamples/android-architecture
今後、さらに簡潔に記述されていくのだろうが、意図は今のが理解しやすいと思いメモ。