さらば proguard、ようこそ R8。

DSL element 'useProguard' is obsolete and will be removed soon. Use 'android.enableR8' in gradle.properties to switch between R8 and Proguard..

DSL要素「useProguard」は廃止されており、まもなく削除されます。 gradle.propertiesで「android.enableR8」を使用して、R8とProguardを切り替えます。

gradle.properties


android.enableR8=true

build.gradle

とりあえずは、いけるが。

以下、編集風景。



しかし、minify とか、proguardFiles とか、どうなるの?

👉 Android Developers Blog: R8, the new code shrinker from Google, is available in Android studio 3.3 beta 


Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option

なんすかね、まったく。

Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option

[Preferences]-[Kotlin Compiler] から、[Target JVM version] を 「1.8」に。

ところが、同じエラーが出たまま。

対処法

以下を、build.gradle に。


android {
  kotlinOptions {
    jvmTarget = '1.8'
  }
}

Android Studio の便利そうに見える設定機能ってほとんどがあやしくね?

👉 intellij idea - Why kotlin gradle plugin cannot build with 1.8 target? - Stack Overflow 


coroutine の使い方 - Android Architecture Blueprints v2 #1

気がついたら「v2」です。MVVM のみとなって、 RxJava は姿を消していますが。

👉 googlesamples/android-architecture: A collection of samples to discuss and showcase different architectural tools and patterns for Android apps. 



現在、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 を使っているか、を定型化しておきたい。

関数 coroutineScope

「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 

今後、さらに簡潔に記述されていくのだろうが、意図は今のが理解しやすいと思いメモ。