Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin コルーチンを 理解しよう 2019 - KotlinFest2019 -

yagi
August 24, 2019

Kotlin コルーチンを 理解しよう 2019 - KotlinFest2019 -

- コルーチンとはなにか、なにがうれしいのか
- Kotlinにおけるコルーチンの仕組み
- Kotlinコルーチンのきほん
- コルーチンスコープと構造化された並行性
- コルーチンと設計
- コルーチンのテスト

yagi

August 24, 2019
Tweet

More Decks by yagi

Other Decks in Technology

Transcript

  1. 1
    Kotlin コルーチンを 理解しよう 2019
    © 2019 Ubie, Inc.
    2019/08/24
    ⼋⽊ 俊広

    View Slide

  2. 2
    About Me
    © 2019 Ubie, Inc.
    ⼋⽊ 俊広
    Toshihiro Yagi
    @sys1yagi
    Software engineer at
    https://techbooster.booth.pm/items/1485567
    TechBooster 【C96新刊】
    Androidϓϩάϥϛϯά୹ฤूɿԦঁͱΧϧςοτͷๅ୳͠

    View Slide

  3. 3
    Ubieについて
    © 2019 Ubie, Inc.
    AIで、医師の業務効率化を⽀援
    患者が使うタブレット問診票と、
    医師が使うエディタ等を開発
    初診問診の事務を1/3に効率化。
    医師が、余裕をもって患者と向き合えるように

    View Slide

  4. 4
    今⽇話すこと
    © 2019 Ubie, Inc.
    • コルーチンとはなにか、なにがうれしいのか
    • Kotlinにおけるコルーチンの仕組み
    • Kotlinコルーチンのきほん
    • コルーチンスコープと構造化された並⾏性
    • コルーチンと設計
    • コルーチンのテスト

    View Slide

  5. 5
    コルーチンとはなにか、
    なにがうれしいのか
    © 2019 Ubie, Inc. 5

    View Slide

  6. 6
    コルーチンとはなにか
    メルヴィン・コンウェイの
    1963年の論⽂が初出
    ※実装は機械語で1958年にあったらしい
    © 2019 Ubie, Inc.
    IUUQNFMDPOXBZDPN)PNFQEGDPNQJMFSQEG

    View Slide

  7. 7
    コルーチンとはなにか
    メルヴィン・コンウェイの
    1963年の論⽂が初出
    ※実装は機械語で1958年にあったらしい
    © 2019 Ubie, Inc.
    IUUQNFMDPOXBZDPN)PNFQEGDPNQJMFSQEG IUUQTUBGGVNFEVNUKTLMUBMLIUNM
    1967年にSimulaがプログラ
    ミング⾔語としては初めて
    コルーチンを実装したらしい

    View Slide

  8. 8
    コルーチンとはなにか
    メルヴィン・コンウェイの
    1963年の論⽂が初出
    ※実装は機械語で1958年にあったらしい
    © 2019 Ubie, Inc.
    IUUQNFMDPOXBZDPN)PNFQEGDPNQJMFSQEG IUUQTUBGGVNFEVNUKTLMUBMLIUNM
    1967年にSimulaがプログラ
    ミング⾔語としては初めて
    コルーチンを実装したらしい
    Modula-2
    様々な⾔語で⾊々な実装
    generator, async/await

    View Slide

  9. 9
    コルーチンは中断と再開を通常の関
    数に導⼊する
    © 2019 Ubie, Inc.

    View Slide

  10. 10
    コルーチンは中断と再開を通常の関数に導⼊する
    © 2019 Ubie, Inc.
    開始
    終了
    ॲཧ"
    ॲཧ#
    ॲཧ$
    通常の関数

    View Slide

  11. 11
    コルーチンは中断と再開を通常の関数に導⼊する
    © 2019 Ubie, Inc.
    開始
    終了
    ॲཧ"
    ॲཧ#
    ॲཧ$
    開始
    終了
    ॲཧ"
    ॲཧ#
    ॲཧ$
    通常の関数 コルーチン
    中断
    再開

    View Slide

  12. 12
    コルーチン同⼠で協調的に動作する(対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    コルーチンA コルーチンB
    XIJMF USVF
    \
    ^
    ॲཧ"
    ॲཧ#
    ॲཧ$
    XIJMF USVF
    \
    ^

    View Slide

  13. 13
    コルーチン同⼠で協調的に動作する(対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    コルーチンA コルーチンB
    XIJMF USVF
    \
    ^
    ॲཧ"
    ॲཧ#
    ॲཧ$
    XIJMF USVF
    \
    ^

    View Slide

  14. 14
    コルーチン同⼠で協調的に動作する(対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    コルーチンA コルーチンB
    XIJMF USVF
    \
    ^
    ॲཧ"
    ॲཧ#
    ॲཧ$
    XIJMF USVF
    \
    ^

    View Slide

  15. 15
    コルーチン同⼠で協調的に動作する(対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    コルーチンA コルーチンB
    XIJMF USVF
    \
    ^
    ॲཧ"
    ॲཧ#
    ॲཧ$
    XIJMF USVF
    \
    ^

    View Slide

  16. 16
    コルーチン同⼠で協調的に動作する(対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    コルーチンA コルーチンB
    XIJMF USVF
    \
    ^
    ॲཧ"
    ॲཧ#
    ॲཧ$
    XIJMF USVF
    \
    ^

    View Slide

  17. 17
    コルーチン同⼠で協調的に動作する(⾮対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    親コルーチン
    ⼦コルーチン
    ॲཧ%
    ॲཧ&

    View Slide

  18. 18
    コルーチン同⼠で協調的に動作する(⾮対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    親コルーチン
    ⼦コルーチン
    ॲཧ%
    ॲཧ&
    ⼦コルーチンが中断したり終
    了すると親に制御を戻す

    View Slide

  19. 19
    コルーチン同⼠で協調的に動作する(⾮対称)
    © 2019 Ubie, Inc.
    ॲཧ"
    ॲཧ#
    ॲཧ$
    中断
    再開
    親コルーチン
    ⼦コルーチン
    ॲཧ%
    ॲཧ&
    スレッド1
    スレッド2
    コルーチンをどのスレッドで実⾏するか切り
    替えたりできる。※⾔語の実装による

    View Slide

  20. 20
    コルーチンはなにがうれしいのか
    © 2019 Ubie, Inc.

    View Slide

  21. 21
    コルーチンはなにがうれしいのか
    © 2019 Ubie, Inc.
    • 継続状況を表現しやすい
    • スレッドより軽量である(Kotlin)

    View Slide

  22. 22
    継続状況を表現しやすい
    © 2019 Ubie, Inc.

    View Slide

  23. 23
    継続状況とはなにか
    © 2019 Ubie, Inc.
    fun getProfile(id: Int, f: (Profile) -> Unit)
    fun loadProfile(id: Int) {
    getProfile(token) { profile ->
    showProfile(profile)
    }
    }

    View Slide

  24. 24
    継続状況とはなにか
    © 2019 Ubie, Inc.
    fun getProfile(id: Int, f: (Profile) -> Unit)
    fun loadProfile(id: Int) {
    getProfile(token) { profile ->
    showProfile(profile)
    }
    }
    APIにリクエストして結果を返す関数

    View Slide

  25. 25
    継続状況とはなにか
    © 2019 Ubie, Inc.
    fun getProfile(id: Int, f: (Profile) -> Unit)
    fun loadProfile(id: Int) {
    getProfile(token) { profile ->
    showProfile(profile)
    }
    }
    結果を受け取るコールバック

    View Slide

  26. 26
    継続状況とはなにか
    © 2019 Ubie, Inc.
    fun getProfile(id: Int, f: (Profile) -> Unit)
    fun loadProfile(id: Int) {
    getProfile(token) { profile ->
    showProfile(profile)
    }
    }

    View Slide

  27. 27
    継続状況とはなにか
    © 2019 Ubie, Inc.
    fun getProfile(id: Int, f: (Profile) -> Unit)
    fun loadProfile(id: Int) {
    getProfile(token) { profile ->
    showProfile(profile)
    }
    }
    コールバックに続きの処理を書く

    View Slide

  28. 28
    継続状況とはなにか
    © 2019 Ubie, Inc.
    fun getProfile(id: Int, f: (Profile) -> Unit)
    fun loadProfile(id: Int) {
    getProfile(token) { profile ->
    showProfile(profile)
    }
    }
    コールバックに続きの処理を書く
    継続状況

    View Slide

  29. 29
    継続状況を表現しやすい
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(token)
    showProfile(profile)
    }

    View Slide

  30. 30
    継続状況を表現しやすい
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(token)
    showProfile(profile)
    }
    ここで中断する

    View Slide

  31. 31
    継続状況を表現しやすい
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(token)
    showProfile(profile)
    }
    再開して続きを処理する

    View Slide

  32. 32
    継続状況を表現しやすい
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(token)
    showProfile(profile)
    }
    再開して続きを処理する
    Kotlinでは実⾏スレッドを任意に変更できる

    View Slide

  33. 33
    スレッドより軽量である(Kotlin)
    © 2019 Ubie, Inc.

    View Slide

  34. 34
    スレッドより軽量である(Kotlin)
    © 2019 Ubie, Inc.
    5ISFBE1PPM
    8PSLFS
    εϨου
    .#d.#΄Ͳ
    ϝϞϦΛ࢖͏
    8PSLFS
    8PSLFS
    8PSLFS

    View Slide

  35. 35
    スレッドより軽量である(Kotlin)
    © 2019 Ubie, Inc.
    5ISFBE1PPM
    8PSLFS
    εϨου
    .#d.#΄Ͳ
    ϝϞϦΛ࢖͏
    8PSLFS
    8PSLFS
    8PSLFS
    $PSPVUJOF
    ͭͷίϧʔν
    ϯͰ,#΄Ͳɻ
    εϨουͰෳ
    ਺࣮ߦ͢Δɻ

    View Slide

  36. 36
    コルーチンとはなにか、なにがうれしいのか
    © 2019 Ubie, Inc.
    • 中断と再開を通常の関数に導⼊する
    • 継続状況を表現しやすい
    • スレッドより軽量である

    View Slide

  37. 37
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.

    View Slide

  38. 38
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile

    View Slide

  39. 39
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    関数に付与することで中断を表す

    View Slide

  40. 40
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    fun loadProfile(id: Int) {
    val profile = getProfile(id)
    showProfile(profile)
    }

    View Slide

  41. 41
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    fun loadProfile(id: Int) {
    val profile = getProfile(id) // Compile Error
    showProfile(profile)
    }

    View Slide

  42. 42
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    fun loadProfile(id: Int) {
    val profile = getProfile(id) // Compile Error
    showProfile(profile)
    } 通常の関数からは呼び出せない

    View Slide

  43. 43
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(id) // OK
    showProfile(profile)
    }

    View Slide

  44. 44
    suspendキーワードをKoltinに導⼊
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(id)
    showProfile(profile)
    }
    開始
    中断
    終了
    再開

    View Slide

  45. 45
    suspendラムダとコルーチン
    © 2019 Ubie, Inc.
    suspend fun getProfile(id: Int): Profile
    suspend fun loadProfile(id: Int) {
    val profile = getProfile(id)
    showProfile(profile)
    }
    通常の関数からsuspend関数が呼び
    出せないとすると、そもそもどう
    やって呼び出せばいいのか?

    View Slide

  46. 46
    suspendラムダとコルーチン
    fun launch(
    block: suspend () -> Unit
    ) { … }
    © 2019 Ubie, Inc.

    View Slide

  47. 47
    suspendラムダとコルーチン
    fun launch(
    block: suspend () -> Unit
    ) { … }
    © 2019 Ubie, Inc.
    関数型にsuspendキーワードをつけたもの
    を、suspendラムダと呼ぶ

    View Slide

  48. 48
    suspendラムダとコルーチン
    fun launch(
    block: suspend () -> Unit
    ) { … }
    © 2019 Ubie, Inc.
    関数型にsuspendキーワードをつけたもの
    を、suspendラムダと呼ぶ
    suspendラムダがコルーチン本体になる

    View Slide

  49. 49
    suspendラムダとコルーチン
    launch {
    val dataSource = ProfileDataSource()
    showLoading()
    dataSource.loadProfile(10)
    hideLoading()
    }
    © 2019 Ubie, Inc.

    View Slide

  50. 50
    suspendラムダとコルーチン
    launch {
    val dataSource = ProfileDataSource()
    showLoading()
    dataSource.loadProfile(10)
    hideLoading()
    }
    © 2019 Ubie, Inc.
    suspend関数を呼び出せる

    View Slide

  51. 51
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    Kotlinプログラム
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    suspendラムダ
    suspend関数
    中断と再開
    コンパイル
    コルーチン

    View Slide

  52. 52
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    Kotlinプログラム
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    suspendラムダ
    suspend関数
    中断と再開
    コンパイル
    コルーチン
    https://speakerdeck.com/sys1yagi/kotlin-korutinwo-li-jie-siyou
    どのようにコルーチンに変換して
    いるかの詳細は前回の資料を参照
    してください

    View Slide

  53. 53
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    Kotlinプログラム
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    suspendラムダのステートマシン
    suspend関数の
    ステートマシン
    CPSによる
    中断と再開
    コンパイル
    コルーチン
    Kotlinはコルーチンのオブジェクト
    を作るところまでサポート

    View Slide

  54. 54
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    Kotlinプログラム
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    suspendラムダのステートマシン
    suspend関数の
    ステートマシン
    CPSによる
    中断と再開
    コンパイル
    コルーチン
    コルーチンライブラリ
    CoroutineDispatcher
    launch, async
    Job, Deferred
    CoroutineScope
    CoroutineStart
    実⾏
    キャンセル
    スレッドプール
    割り込み
    例外ハンドラ
    スコープ etc

    View Slide

  55. 55
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    Kotlinプログラム
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    suspendラムダのステートマシン
    suspend関数の
    ステートマシン
    CPSによる
    中断と再開
    コンパイル
    コルーチン
    コルーチンライブラリ
    CoroutineDispatcher
    launch, async
    Job, Deferred
    CoroutineScope
    CoroutineStart
    コルーチンの実⾏や管理
    はライブラリが提供する
    実⾏
    キャンセル
    スレッドプール
    割り込み
    例外ハンドラ
    スコープ etc

    View Slide

  56. 56
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    Kotlinプログラム
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    suspendラムダのステートマシン
    suspend関数の
    ステートマシン
    CPSによる
    中断と再開
    コンパイル
    コルーチン
    コルーチンライブラリ
    CoroutineDispatcher
    launch, async
    Job, Deferred
    CoroutineScope
    CoroutineStart
    コルーチンの実⾏や管理
    はライブラリが提供する
    実⾏
    キャンセル
    スレッドプール
    割り込み
    例外ハンドラ
    スコープ etc
    Kotlinのコルーチンの話はほぼこのラ
    イブラリの使い⽅の話

    View Slide

  57. 57
    Kotlinにおけるコルーチンの仕組み
    © 2019 Ubie, Inc.
    • suspendキーワードを導⼊し、中断を表現するようにした
    • suspendラムダがコルーチンの本体になる。suspendラムダは通
    常の関数の引数になる
    • Kotlin⾃体はコルーチンのオブジェクトを作るところまで
    • コルーチンライブラリが実⾏などを管理する仕組みを提供する

    View Slide

  58. 58
    Kotlinコルーチンのきほん
    © 2019 Ubie, Inc.

    View Slide

  59. 59
    コルーチンライブラリを導⼊する
    © 2019 Ubie, Inc.
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:
    1.3.0"
    implementation “org.jetbrains.kotlinx:kotlinx-coroutines-android:
    1.3.0-RC2”
    implementation “org.jetbrains.kotlinx:kotlinx-coroutines-play-services:
    1.3.0-RC2”
    implementation “org.jetbrains.kotlinx:kotlinx-coroutines-rx2:
    1.3.0-RC2”
    https://github.com/Kotlin/kotlinx.coroutines
    build.gradle

    View Slide

  60. 60
    コルーチンライブラリを導⼊する
    © 2019 Ubie, Inc.
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:
    1.3.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:
    1.3.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:
    1.3.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:
    1.3.0"
    https://github.com/Kotlin/kotlinx.coroutines
    build.gradle

    View Slide

  61. 61
    最初のコルーチン
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  62. 62
    最初のコルーチン
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }
    Hello,
    World!

    View Slide

  63. 63
    最初のコルーチン
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }
    コルーチンスコープ

    View Slide

  64. 64
    最初のコルーチン
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }
    コルーチンスコープ
    コルーチンビルダー関数

    View Slide

  65. 65
    最初のコルーチン
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }
    コルーチンスコープ
    ライブラリが提供するのsuspend関数
    コルーチンビルダー関数

    View Slide

  66. 66
    コルーチンスコープ
    © 2019 Ubie, Inc.
    • コルーチンが動作する範囲を表す
    • コルーチンスコープの中でのみコ
    ルーチンを起動できる
    • 複数のコルーチンを持てる
    • ライフサイクルがあり、スコープを
    閉じると、中のコルーチンも閉じら
    れる
    コルーチンA
    コルーチンスコープ
    コルーチンB
    コルーチンC

    View Slide

  67. 67
    コルーチンビルダー関数
    © 2019 Ubie, Inc.
    • コルーチンを起動する関数
    • 実⾏コンテキスト、開始⽅
    法、suspendラムダを渡して
    コルーチンを起動できる
    • ノンブロッキングで動作する
    • 結果を返さないlaunch関数
    と、結果を返すasync関数が
    ある。
    public fun CoroutineScope.launch(
    context: CoroutineContext
    start: CoroutineStart
    block: suspend CoroutineScope.() -> Unit
    ): Job
    public fun CoroutineScope.async(
    context: CoroutineContext
    start: CoroutineStart
    block: suspend CoroutineScope.() -> T
    ): Deferred

    View Slide

  68. 68
    suspend関数
    © 2019 Ubie, Inc.
    • 中断を表す関数
    • suspend関数はsuspend関数
    からしか呼び出せない
    • 関数にsuspendキーワードを
    付与することで宣⾔できる
    public suspend fun delay(timeMillis: Long)

    View Slide

  69. 69
    最初のコルーチン
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  70. 70
    最初のコルーチン
    © 2019 Ubie, Inc.

    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  71. 71
    最初のコルーチン
    © 2019 Ubie, Inc.


    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  72. 72
    最初のコルーチン
    © 2019 Ubie, Inc.



    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  73. 73
    最初のコルーチン
    © 2019 Ubie, Inc.




    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  74. 74
    最初のコルーチン
    © 2019 Ubie, Inc.




    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }

    View Slide

  75. 75
    コルーチンとブロッキング
    © 2019 Ubie, Inc.
    fun main() {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(200)
    }
    他の⽅法はないか…?

    View Slide

  76. fun main() = runBlocking {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    76
    コルーチンとブロッキング
    © 2019 Ubie, Inc.

    View Slide

  77. fun main() = runBlocking {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    77
    コルーチンとブロッキング
    © 2019 Ubie, Inc.
    ブロッキングでコルーチンを実⾏できる

    View Slide

  78. fun main() = runBlocking {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    78
    コルーチンとブロッキング
    © 2019 Ubie, Inc.

    View Slide

  79. fun main() = runBlocking {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    79
    コルーチンとブロッキング
    © 2019 Ubie, Inc.
    どちらにせよ待つ必要がある

    View Slide

  80. fun main() = runBlocking {
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    80
    コルーチンとブロッキング
    © 2019 Ubie, Inc.
    どちらにせよ待つ必要がある
    launch関数の完了を待つ⽅法は他にないか?

    View Slide

  81. fun main() = runBlocking { // this: CoroutineScope
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    81
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.

    View Slide

  82. fun main() = runBlocking { // this: CoroutineScope
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    82
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.
    block内はCoroutineScopeがレシーバ

    View Slide

  83. fun main() = runBlocking { // this: CoroutineScope
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    83
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.
    トップレベルのスコープ。アプリケーション
    (プロセス)のライフサイクルで動作する

    View Slide

  84. fun main() = runBlocking { // this: CoroutineScope
    GlobalScope.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    84
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.
    このコルーチンはrunBlockingのスコープ
    に属さないため、明⽰的に終了を待つ必要
    がある

    View Slide

  85. fun main() = runBlocking { // this: CoroutineScope
    this.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    85
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.

    View Slide

  86. fun main() = runBlocking { // this: CoroutineScope
    this.launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    delay(200)
    }
    86
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.
    runBlockingの⼦コルーチンとして起動する

    View Slide

  87. fun main() = runBlocking { // this: CoroutineScope
    launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    }
    87
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.

    View Slide

  88. fun main() = runBlocking { // this: CoroutineScope
    launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    }
    88
    コルーチンスコープと⼦コルーチン
    © 2019 Ubie, Inc.
    スコープは⼦コルーチンがすべて完了するまで待ってくれる

    View Slide

  89. fun main() = runBlocking { // this: CoroutineScope
    launch {
    delay(100)
    println("World!")
    }
    println("Hello,")
    }
    89
    コルーチンのキャンセル
    © 2019 Ubie, Inc.

    View Slide

  90. fun main() = runBlocking { // this: CoroutineScope
    launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    }
    90
    コルーチンのキャンセル
    © 2019 Ubie, Inc.

    View Slide

  91. fun main() = runBlocking { // this: CoroutineScope
    launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    }
    91
    コルーチンのキャンセル
    © 2019 Ubie, Inc.
    時間経過では終了しないコルーチン

    View Slide

  92. fun main() = runBlocking { // this: CoroutineScope
    launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    }
    92
    コルーチンのキャンセル
    © 2019 Ubie, Inc.
    ずっと待ってしまう

    View Slide

  93. 93
    コルーチンのキャンセル
    © 2019 Ubie, Inc.
    public fun CoroutineScope.launch(
    context: CoroutineContext
    start: CoroutineStart
    block: suspend CoroutineScope.() -> Unit
    ): Job
    fun main() = runBlocking { // this: CoroutineScope
    val job = launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    }

    View Slide

  94. fun main() = runBlocking { // this: CoroutineScope
    val job = launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    delay(500)
    job.cancel()
    }
    94
    コルーチンのキャンセル
    © 2019 Ubie, Inc.

    View Slide

  95. fun main() = runBlocking { // this: CoroutineScope
    val job = launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    delay(500)
    job.cancel()
    }
    95
    コルーチンのキャンセル
    © 2019 Ubie, Inc.
    jobを使ってキャンセルできる

    View Slide

  96. fun main() = runBlocking { // this: CoroutineScope
    val job = launch {
    while(true) {
    delay(100)
    println("World!")
    }
    }
    println("Hello,")
    delay(500)
    job.cancel()
    }
    96
    コルーチンのキャンセル
    © 2019 Ubie, Inc.
    Hello,
    World!
    World!
    World!
    World!

    View Slide

  97. 97
    Kotlinコルーチンのきほん①
    © 2019 Ubie, Inc.
    • コルーチンスコープはコルーチンが動作する範囲で、複数のコルーチンを起動できる
    • コルーチンを起動するためのコルーチンビルダー関数(launch, async)があり、実
    ⾏コンテキスト、開始⽅法とsuspendラムダを渡して使う
    • コルーチンをブロッキングで実⾏するためのrunBlocking関数がある
    • コルーチンスコープは⼦コルーチンの完了を待つ
    • 時間経過で完了するコルーチンと、完了しないコルーチンがある。
    • Job#cancel関数を使って明⽰的にコルーチンをキャンセルできる。

    View Slide

  98. 98
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile {
    return apiClient.getProfile(id)
    }
    }

    View Slide

  99. 99
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile {
    return apiClient.getProfile(id)
    }
    }
    APIにリクエストするクライアント

    View Slide

  100. 100
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile {
    return apiClient.getProfile(id)
    }
    }
    ブロッキングでリクエストする関数

    View Slide

  101. 101
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile {
    return apiClient.getProfile(id)
    }
    }
    この関数はどのスレッドで動作するか?

    View Slide

  102. 102
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile {
    return apiClient.getProfile(id)
    }
    }
    この関数はどのスレッドで動作するか?
    関数を呼び出したスレッドで動作する

    View Slide

  103. 103
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }

    View Slide

  104. 104
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    } 実⾏コンテキストを切り替える関数

    View Slide

  105. 105
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }
    コルーチンディスパッチャーの定数

    View Slide

  106. 106
    実⾏コンテキスト
    © 2019 Ubie, Inc.
    • コルーチンの中で共有する要素の
    セット
    • Job、コルーチンインターセプ
    ター、例外ハンドラなどコルーチン
    の動作に必要な要素を含んでいる
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    コルーチン
    実⾏コンテキスト

    View Slide

  107. 107
    コルーチンディスパッチャー
    © 2019 Ubie, Inc.
    • コルーチンインターセプターの実装
    • コルーチンをどのスレッドで実⾏するかを
    選択できる
    • 定数として4種類提供している
    • Default: CPUバウンド
    • IO: I/Oバウンド
    • Main: メインスレッド
    • UnConfined: 制限なし(通常は使わない)
    ঢ়ଶ
    ঢ়ଶ
    ঢ়ଶ
    コルーチン
    実⾏コンテキスト
    ίϧʔνϯσΟεύονϟʔ
    実⾏スレッドをコントロール

    View Slide

  108. 108
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }

    View Slide

  109. 109
    suspend関数と実⾏コンテキスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    } IOバウンドのスレッドプールで動
    作するように切り替える

    View Slide

  110. 110
    suspend関数の呼び出し
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    println("loadProfile: ${Thread.currentThread().id}")
    apiClient.getProfile(id)
    }
    }

    View Slide

  111. 111
    suspend関数の呼び出し
    © 2019 Ubie, Inc.
    fun main() = runBlocking {
    println("start: ${Thread.currentThread().id}")
    val dataSource = ProfileDataSource(ApiClient())
    val profile = dataSource.loadProfile(10)
    println("end: ${Thread.currentThread().id}")
    }

    View Slide

  112. 112
    suspend関数の呼び出し
    © 2019 Ubie, Inc.
    fun main() = runBlocking {
    println("start: ${Thread.currentThread().id}")
    val dataSource = ProfileDataSource(ApiClient())
    val profile = dataSource.loadProfile(10)
    println("end: ${Thread.currentThread().id}")
    }
    start: 1
    loadProfile: 8
    end: 1

    View Slide

  113. 113
    例外のハンドリング
    © 2019 Ubie, Inc.
    fun main() = runBlocking {
    println("start: ${Thread.currentThread().id}")
    val dataSource = ProfileDataSource(ApiClient())
    val profile = dataSource.loadProfile(10)
    println("end: ${Thread.currentThread().id}")
    }
    通信エラー

    View Slide

  114. 114
    例外のハンドリング
    © 2019 Ubie, Inc.
    fun main() = runBlocking {
    println("start: ${Thread.currentThread().id}")
    val dataSource = ProfileDataSource(ApiClient())
    try {
    val profile = dataSource.loadProfile(10)
    println("end: ${Thread.currentThread().id}")
    } catch (e: Exception) {
    println("error")
    }
    }

    View Slide

  115. 115
    例外のハンドリング
    © 2019 Ubie, Inc.
    fun main() = runBlocking {
    println("start: ${Thread.currentThread().id}")
    val dataSource = ProfileDataSource(ApiClient())
    try {
    val profile = dataSource.loadProfile(10)
    println("end: ${Thread.currentThread().id}")
    } catch (e: Exception) {
    println("error")
    }
    }
    基本的にsuspend関数をtry-catchで
    囲む形で良い

    View Slide

  116. 116
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }

    View Slide

  117. 117
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    JetpackのViewModel

    View Slide

  118. 118
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    ActivityやFragmentから呼び出す

    View Slide

  119. 119
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    lifecycle-viewmodel-ktx:2.2.0-alpha3
    が提供するViewModelのコルーチンスコープ

    View Slide

  120. 120
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    メインスレッドで動作する

    View Slide

  121. 121
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    この関数はDispatchers.IOで動作する

    View Slide

  122. 122
    実際に近い例(Android)
    © 2019 Ubie, Inc.
    class ProfileViewModel(private val dataSource: ProfileDataSource) : ViewModel() {
    sealed class ViewState { /* ུ */ }
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun loadProfile(id: Long) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    dataSource.loadProfile(id)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }

    View Slide

  123. 123
    Kotlinコルーチンのきほん②
    © 2019 Ubie, Inc.
    • コルーチンが動作するための要素セットとして実⾏コンテキストがある
    • ⽤途ごとにスレッドプールなどをコルーチンディスパッチャーとして
    提供している
    • withContext関数で実⾏コンテキストを切り替えられる
    • 例外ハンドリングは基本的にtry-catchで⾏う
    • ⾮同期処理のほとんどはこれらの組み合わせで対応できる

    View Slide

  124. 124
    コルーチンスコープと
    構造化された並⾏性
    © 2019 Ubie, Inc.

    View Slide

  125. 125
    直列な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile = dataSource.loadProfile(id)
    val articles = dataSource.loadArticles(id)
    showContent(profile, articles)
    hideProgress()
    }
    }

    View Slide

  126. 126
    直列な動作
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile = dataSource.loadProfile(id) --- 1
    val articles = dataSource.loadArticles(id) --- 2
    showContent(profile, articles)
    hideProgress()
    }
    }
    © 2019 Ubie, Inc.
    順次、IOバウンドのスレッドプールで実⾏する

    View Slide

  127. 127
    直列な動作
    © 2019 Ubie, Inc.
    順次、IOバウンドのスレッドプールで実⾏する
    同時に実⾏するには?
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile = dataSource.loadProfile(id) --- 1
    val articles = dataSource.loadArticles(id) --- 2
    showContent(profile, articles)
    hideProgress()
    }
    }

    View Slide

  128. 128
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    hideProgress()
    }
    }

    View Slide

  129. 129
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    hideProgress()
    }
    }
    Deferredを返し、コルーチンを実⾏する

    View Slide

  130. 130
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    hideProgress()
    }
    } Deferredから結果を取り出す

    View Slide

  131. 131
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    hideProgress()
    }
    }
    通信エラー

    View Slide

  132. 132
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    try {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }

    View Slide

  133. 133
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    try {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }

    View Slide

  134. 134
    並⾏な動作
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    try {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }

    View Slide

  135. 135
    構造化された並⾏性
    © 2019 Ubie, Inc.
    IUUQTNFEJVNDPN!FMJ[BSPWTUSVDUVSFEDPODVSSFODZEBB

    View Slide

  136. 136
    構造化された並⾏性
    © 2019 Ubie, Inc.
    • 並⾏処理の分岐を構造化するとい
    う考え⽅
    • 分岐と合流の範囲を明⽰的に宣⾔
    することでエラーの範囲が決まる
    • コルーチンスコープがひとつの分
    岐と合流を表す
    • コルーチンスコープの親⼦関係で
    並⾏性を構造化する
    分岐
    合流
    コルーチン
    スコープ
    launch/async

    View Slide

  137. 137
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    try {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }

    View Slide

  138. 138
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    try {
    coroutineScope {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    }
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }

    View Slide

  139. 139
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    fun loadContent() {
    viewModelScope.launch {
    try {
    coroutineScope {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    }
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    ⼦スコープを作る関数

    View Slide

  140. fun loadContent() {
    viewModelScope.launch {
    try {
    coroutineScope {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    }
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    140
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    親スコープ
    coroutineScope
    viewModelScope.launch()
    async()
    async()

    View Slide

  141. fun loadContent() {
    viewModelScope.launch {
    try {
    coroutineScope {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    }
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    viewModelScope.launch()
    141
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    親スコープ
    coroutineScope
    async()
    async()
    この範囲が処理の単位になる

    View Slide

  142. fun loadContent() {
    viewModelScope.launch {
    try {
    coroutineScope {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    }
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    142
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    親スコープ
    coroutineScope
    async()
    async()
    どれかが失敗すると全体がエラーになる
    viewModelScope.launch()
    この範囲が処理の単位になる

    View Slide

  143. fun loadContent() {
    viewModelScope.launch {
    try {
    coroutineScope {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    }
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    143
    並⾏性を構造化する
    © 2019 Ubie, Inc.
    親スコープ
    coroutineScope
    async()
    async()
    スコープのエラーを透過的にハンドリングできる
    viewModelScope.launch()

    View Slide

  144. fun loadContent() {
    viewModelScope.launch {
    try {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    144
    並⾏性を構造化していなかったら
    © 2019 Ubie, Inc.
    親スコープ
    async()
    async()
    viewModelScope.launch()

    View Slide

  145. fun loadContent() {
    viewModelScope.launch {
    try {
    showProgress()
    val id = 10L
    val profile: Deferred = async { dataSource.loadProfile(id) }
    val articles: Deferred> = async { dataSource.loadArticles(id) }
    showContent(profile.await(), articles.await())
    } catch (e: IOException) {
    showError(e)
    } finally {
    hideProgress()
    }
    }
    }
    145
    並⾏性を構造化していなかったら
    © 2019 Ubie, Inc.
    親スコープ
    async()
    async()
    この範囲が処理の単位になるので、
    エラーが発⽣すると、親スコープが終了する
    viewModelScope.launch()

    View Slide

  146. 146
    コルーチンスコープと構造化された並⾏性
    © 2019 Ubie, Inc.
    • コルーチンを直列で実⾏する場合は実⾏コンテキスト切り替え以外は基本
    的に気にしなくてよい
    • コルーチンを分岐させる場合は並⾏性を構造化する必要がある
    • 並⾏性の構造化とは分岐と合流の範囲を明⽰すること
    • launch/asyncが分岐を表し、コルーチンスコープが範囲を表す
    • coroutineScope関数を使って⼦スコープ(分岐と合流の範囲)を作るこ
    とができる
    • 分岐と合流の範囲を構造化することで、処理とエラーの範囲を決められる

    View Slide

  147. 147
    コルーチンと設計
    © 2019 Ubie, Inc.

    View Slide

  148. 148
    コルーチンと設計
    © 2019 Ubie, Inc.
    • コルーチンスコープとアプリケーションライフ
    サイクルを合わせる
    • suspend関数はメインセーフティで実装する
    • コルーチンはメインセーフティで起動する

    View Slide

  149. 149
    コルーチンと設計
    © 2019 Ubie, Inc.
    • コルーチンスコープとアプリケーションライフ
    サイクルを合わせる
    • suspend関数はメインセーフティで実装する
    • コルーチンはメインセーフティで起動する

    View Slide

  150. 150
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    val scope = CoroutineScope(Dispatchers.Main + Job())
    scope.launch { ... }
    scope.coroutineContext.cancelChildren()

    View Slide

  151. 151
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    val scope = CoroutineScope(Dispatchers.Main + Job())
    scope.launch { ... }
    scope.coroutineContext.cancelChildren()
    実⾏コンテキストの設定とともに
    コルーチンスコープを作れる

    View Slide

  152. 152
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    val scope = CoroutineScope(Dispatchers.Main + Job())
    scope.launch { ... }
    scope.coroutineContext.cancelChildren()
    スコープをつかってコルーチンを起動する

    View Slide

  153. 153
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    val scope = CoroutineScope(Dispatchers.Main + Job())
    scope.launch { ... }
    scope.coroutineContext.cancelChildren()
    スコープを破棄するときにキャンセ
    ルしてリークを防ぐ

    View Slide

  154. 154
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    プロセス
    UIアプリケーション(Android)
    Activity
    Fragment
    Viewmodel
    サーバーサイドアプリケーション(ktor)
    プロセス
    リクエスト
    LiveData

    View Slide

  155. 155
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    プロセス
    UIアプリケーション(Android)
    Viewmodel
    GlobalScope
    Activity
    Fragment
    LiveData

    View Slide

  156. 156
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    プロセス
    UIアプリケーション(Android)
    Viewmodel
    GlobalScope
    lifecylceScope
    Activity
    Fragment
    lifecycle-runtime-ktx:2.2.0-alpha01 or higher
    LiveData

    View Slide

  157. 157
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    プロセス
    UIアプリケーション(Android)
    Viewmodel
    GlobalScope
    lifecylceScope
    Activity
    Fragment
    lifecycle-runtime-ktx:2.2.0-alpha01 or higher
    viewModelScope
    lifecycle-viewmodel-ktx:2.1.0-beta01 or higher
    LiveData

    View Slide

  158. 158
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    プロセス
    UIアプリケーション(Android)
    Viewmodel
    GlobalScope
    lifecylceScope
    Activity
    Fragment
    lifecycle-runtime-ktx:2.2.0-alpha01 or higher
    viewModelScope
    lifecycle-viewmodel-ktx:2.1.0-beta01 or higher
    LiveData
    liveData
    lifecycle-livedata-ktx:2.2.0-alpha01 or higher

    View Slide

  159. 159
    コルーチンスコープとアプリケーションライフサイクルを合わせる
    © 2019 Ubie, Inc.
    プロセス
    UIアプリケーション(Android)
    Viewmodel
    GlobalScope
    lifecylceScope
    Activity
    Fragment
    lifecycle-runtime-ktx:2.2.0-alpha01 or higher
    viewModelScope
    lifecycle-viewmodel-ktx:2.1.0-beta01 or higher
    LiveData
    liveData
    lifecycle-livedata-ktx:2.2.0-alpha01 or higher
    幸いなことにKotlinコルーチンのサポートが
    様々なプラットフォームで進んでおり、⾃前で
    ⽤意する必要はほとんどなくなった

    View Slide

  160. 160
    コルーチンと設計
    © 2019 Ubie, Inc.
    • コルーチンスコープとアプリケーションライフ
    サイクルを合わせる
    • suspend関数はメインセーフティで実装する
    • コルーチンはメインセーフティで起動する

    View Slide

  161. 161
    suspend関数はメインセーフティで実装する
    © 2019 Ubie, Inc.
    メインスレッド
    処理の分類
    ほかのスレッド
    CPUバウンド
    I/Oバウンド
    数値計算
    ソート、変換
    画像処理
    ライフサイクルメソッド
    イベントハンドリング
    UIの操作
    ファイル操作
    ネットワークアクセス
    データベースアクセス

    View Slide

  162. 162
    suspend関数はメインセーフティで実装する
    © 2019 Ubie, Inc.
    メインスレッド
    処理の分類
    ほかのスレッド
    CPUバウンド
    I/Oバウンド
    数値計算
    ソート、変換
    画像処理
    ライフサイクルメソッド
    イベントハンドリング
    UIの操作
    ファイル操作
    ネットワークアクセス
    データベースアクセス
    メインスレッド以外で処理する場合、
    suspend関数をメインスレッドから安全
    に呼び出せるように実装する

    View Slide

  163. 163
    suspend関数はメインセーフティで実装する
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }

    View Slide

  164. 164
    suspend関数はメインセーフティで実装する
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }
    ブロッキングする関数を使ってるので...

    View Slide

  165. 165
    suspend関数はメインセーフティで実装する
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    } メインスレッドから安全に呼び出せるようにする

    View Slide

  166. 166
    suspend関数はメインセーフティで実装する
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    } メインスレッドから安全に呼び出せるようにする
    suspend関数をメインスレッドから呼び出す時に、実⾏ス
    レッドを意識させない

    View Slide

  167. 167
    コルーチンと設計
    © 2019 Ubie, Inc.
    • コルーチンスコープとアプリケーションライフ
    サイクルを合わせる
    • suspend関数はメインセーフティで実装する
    • コルーチンはメインセーフティで起動する

    View Slide

  168. 168
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch {
    try {
    viewState.value = ViewState.Progress
    profile.value = dataSource.loadProfile(10L)
    } catch (e: IOException) {
    viewState.value = ViewState.Error(e)
    } finally {
    viewState.value = ViewState.Loaded
    }
    }
    }
    (PPE

    View Slide

  169. 169
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch {
    try {
    viewState.value = ViewState.Progress
    profile.value = dataSource.loadProfile(10L)
    } catch (e: IOException) {
    viewState.value = ViewState.Error(e)
    } finally {
    viewState.value = ViewState.Loaded
    }
    }
    }
    Android JetpackのViewModel
    (PPE

    View Slide

  170. 170
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch {
    try {
    viewState.value = ViewState.Progress
    profile.value = dataSource.loadProfile(10L)
    } catch (e: IOException) {
    viewState.value = ViewState.Error(e)
    } finally {
    viewState.value = ViewState.Loaded
    }
    }
    }
    ViewModelのコルーチンスコープ
    Mainディスパッチャーで動作する
    (PPE

    View Slide

  171. 171
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch {
    try {
    viewState.value = ViewState.Progress
    profile.value = dataSource.loadProfile(10L)
    } catch (e: IOException) {
    viewState.value = ViewState.Error(e)
    } finally {
    viewState.value = ViewState.Loaded
    }
    }
    }
    メインスレッドで動作する
    (PPE

    View Slide

  172. 172
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch(Dispatchers.IO) {
    try {
    viewState.postValue(ViewState.Progress)
    profile.postValue(dataSource.loadProfile(10L))
    } catch (e: IOException) {
    viewState.postValue(ViewState.Error(e))
    } finally {
    viewState.postValue(ViewState.Loaded)
    }
    }
    }
    /PU(PPE

    View Slide

  173. 173
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch(Dispatchers.IO) {
    try {
    viewState.postValue(ViewState.Progress)
    profile.postValue(dataSource.loadProfile(10L))
    } catch (e: IOException) {
    viewState.postValue(ViewState.Error(e))
    } finally {
    viewState.postValue(ViewState.Loaded)
    }
    }
    }
    異なるスレッドで動かす
    /PU(PPE

    View Slide

  174. 174
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch(Dispatchers.IO) {
    try {
    viewState.postValue(ViewState.Progress)
    profile.postValue(dataSource.loadProfile(10L))
    } catch (e: IOException) {
    viewState.postValue(ViewState.Error(e))
    } finally {
    viewState.postValue(ViewState.Loaded)
    }
    }
    }
    ⼀応LiveDataの場合postValueを使えばメイン
    セーフティな操作ができる
    /PU(PPE

    View Slide

  175. 175
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch(Dispatchers.IO) {
    try {
    viewState.postValue(ViewState.Progress)
    profile.postValue(dataSource.loadProfile(10L))
    } catch (e: IOException) {
    viewState.postValue(ViewState.Error(e))
    } finally {
    viewState.postValue(ViewState.Loaded)
    }
    }
    }
    ブロッキングする関数だとしても
    問題なく動作できるが...
    /PU(PPE

    View Slide

  176. 176
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch(Dispatchers.IO) {
    try {
    viewState.postValue(ViewState.Progress)
    profile.postValue(dataSource.loadProfile(10L))
    } catch (e: IOException) {
    viewState.postValue(ViewState.Error(e))
    } finally {
    viewState.postValue(ViewState.Loaded)
    }
    }
    }
    ブロッキングする関数だとしても
    問題なく動作できるが...
    メインセーフティでない関数を呼び
    出すかもしれないことを意識させる
    べきではない /PU(PPE

    View Slide

  177. 177
    コルーチンはメインセーフティで起動する
    © 2019 Ubie, Inc.
    class MainViewModel(val dataSource: ProfileDataSource) : ViewModel() {
    val profile: LiveData
    val viewState: LiveData
    fun loadContent() {
    viewModelScope.launch {
    try {
    viewState.value = ViewState.Progress
    profile.value = dataSource.loadProfile(10L)
    } catch (e: IOException) {
    viewState.value = ViewState.Error(e)
    } finally {
    viewState.value = ViewState.Loaded
    }
    }
    }
    (PPE

    View Slide

  178. 178
    コルーチンと設計
    © 2019 Ubie, Inc.
    • コルーチンスコープとアプリケーションライフサイクルを合
    わせる。最近ではプラットフォームで公式のサポートをして
    くれている
    • suspend関数はメインセーフティで実装する
    • コルーチンはメインセーフティで起動する。もし問題があれ
    ばsuspend関数がメインセーフティでないかもしれない

    View Slide

  179. 179
    コルーチンのテスト
    © 2019 Ubie, Inc.

    View Slide

  180. 180
    コルーチンのテスト
    © 2019 Ubie, Inc.
    suspend関数をテストする runBlocking関数を使う
    間接的にコルーチンを起動する
    Yes
    No
    ↑に加えて
    runBlockingTest関数と
    setMain関数を使う
    メインセーフティで呼び出し、
    ディスパッチャーを差し替えるorモックする
    単純に完了を待つ
    実⾏タイミングを制御したい

    View Slide

  181. 181
    コルーチンのテストライブラリ
    © 2019 Ubie, Inc.
    https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test
    • 実⾏タイミングを制御するテストディスパッチャーとrunBlockingTest関数
    • Mainディスパッチャーを書き換えるsetMain関数

    View Slide

  182. 182
    コルーチンのテストライブラリ
    © 2019 Ubie, Inc.
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:
    1.3.0"
    testImplementation "androidx.test.ext:truth:1.2.0"
    testImplementation "io.mockk:mockk:1.8.13.kotlin13"
    testImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
    build.gradle (Android)

    View Slide

  183. 183
    コルーチンのテストライブラリ
    © 2019 Ubie, Inc.
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:
    1.3.0-RC2"
    testImplementation "androidx.test.ext:truth:1.2.0"
    testImplementation "io.mockk:mockk:1.8.13.kotlin13"
    testImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
    build.gradle (Android)
    アサーションとモックライブラリ

    View Slide

  184. 184
    コルーチンのテスト
    © 2019 Ubie, Inc.
    suspend関数をテストする runBlocking関数を使う
    間接的にコルーチンを起動する
    Yes
    No
    ↑に加えて
    runBlockingTest関数と
    setMain関数を使う
    メインセーフティで呼び出し、
    ディスパッチャーを差し替えるorモックする
    単純に完了を待つ
    実⾏タイミングを制御したい

    View Slide

  185. 185
    suspend関数のテスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }

    View Slide

  186. 186
    suspend関数のテスト
    © 2019 Ubie, Inc.
    @Test
    fun loadProfileSuccess(): Unit = runBlocking {
    val dataSource = ProfileDataSource(mockk {
    every { getProfile(any()) } returns Profile()
    })
    val profile = dataSource.loadProfile(10)
    assertThat(profile).isNotNull()
    }
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }

    View Slide

  187. 187
    suspend関数のテスト
    © 2019 Ubie, Inc.
    @Test
    fun loadProfileSuccess(): Unit = runBlocking {
    val dataSource = ProfileDataSource(mockk {
    every { getProfile(any()) } returns Profile()
    })
    val profile = dataSource.loadProfile(10)
    assertThat(profile).isNotNull()
    }
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    } ブロッキングでコルーチンを実⾏

    View Slide

  188. 188
    suspend関数のテスト
    © 2019 Ubie, Inc.
    @Test
    fun loadProfileSuccess(): Unit = runBlocking {
    val dataSource = ProfileDataSource(mockk {
    every { getProfile(any()) } returns Profile()
    })
    val profile = dataSource.loadProfile(10)
    assertThat(profile).isNotNull()
    }
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }
    ApiClientをモック

    View Slide

  189. 189
    suspend関数のテスト
    © 2019 Ubie, Inc.
    @Test
    fun loadProfileSuccess(): Unit = runBlocking {
    val dataSource = ProfileDataSource(mockk {
    every { getProfile(any()) } returns Profile()
    })
    val profile = dataSource.loadProfile(10)
    assertThat(profile).isNotNull()
    }
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }
    suspend関数を呼び出す

    View Slide

  190. 190
    suspend関数のテスト
    © 2019 Ubie, Inc.
    @Test
    fun loadProfileSuccess(): Unit = runBlocking {
    val dataSource = ProfileDataSource(mockk {
    every { getProfile(any()) } returns Profile()
    })
    val profile = dataSource.loadProfile(10)
    assertThat(profile).isNotNull()
    }
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }
    アサーション

    View Slide

  191. 191
    suspend関数のテスト
    © 2019 Ubie, Inc.
    @Test
    fun loadProfileSuccess(): Unit = runBlocking {
    val dataSource = ProfileDataSource(mockk {
    every { getProfile(any()) } returns Profile()
    })
    val profile = dataSource.loadProfile(10)
    assertThat(profile).isNotNull()
    }
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    apiClient.getProfile(id)
    }
    }

    View Slide

  192. 192
    suspend関数のテスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    coroutineScope {
    val profile = async { apiClient.getProfile(id) }
    val articles = async { apiClient.getArticles(id) }
    Profile(profile.await(), articles.await())
    }
    }
    }

    View Slide

  193. 193
    suspend関数のテスト
    © 2019 Ubie, Inc.
    class ProfileDataSource(private val apiClient: ApiClient) {
    suspend fun loadProfile(id: Long): Profile = withContext(Dispatchers.IO) {
    coroutineScope {
    val profile = async { apiClient.getProfile(id) }
    val articles = async { apiClient.getArticles(id) }
    Profile(profile.await(), articles.await())
    }
    }
    }
    呼び出し元と異なるスコープでコルーチン
    を起動しない限りはこういう形などでも問
    題ない

    View Slide

  194. 194
    コルーチンのテスト
    © 2019 Ubie, Inc.
    suspend関数をテストする runBlocking関数を使う
    間接的にコルーチンを起動する
    Yes
    No
    ↑に加えて
    runBlockingTest関数と
    setMain関数を使う
    メインセーフティで呼び出し、
    ディスパッチャーを差し替えるorモックする
    単純に完了を待つ
    実⾏タイミングを制御したい

    View Slide

  195. 195
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }

    View Slide

  196. 196
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    JetpackのViewModel

    View Slide

  197. 197
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    JetpackのRoomで、DAOでsuspend関数を宣⾔している

    View Slide

  198. 198
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    通常の関数呼び出しからコルーチンを起動する

    View Slide

  199. 199
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    suspend関数を呼び出す

    View Slide

  200. 200
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }

    View Slide

  201. 201
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    Roomを動かすためにAndroidJUnit4で動かす

    View Slide

  202. 202
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    RoomDatabaseを初期化する

    View Slide

  203. 203
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    アサーションのためにDAOを操作す
    るのでrunBlockingで動かす

    View Slide

  204. 204
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    Caused by: expected: 1
    but was : 0

    View Slide

  205. 205
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }

    View Slide

  206. 206
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    テストではここまで到達して中断し、制御
    が戻る(アサーションに進んでしまう)

    View Slide

  207. 207
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch(Dispatchers.IO) {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    もしメインセーフティな実⾏
    じゃない場合はここで⽌まる
    /PU(PPE

    View Slide

  208. 208
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    この実⾏をテストスレッドで⾏い
    たい

    View Slide

  209. 209
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }

    View Slide

  210. 210
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .setQueryExecutor { it.run() }
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }

    View Slide

  211. 211
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .setQueryExecutor { it.run() }
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    テストスレッドで実⾏するようにする

    View Slide

  212. 212
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .setQueryExecutor { it.run() }
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    テストスレッドで実⾏するようにする
    Main以外のディスパッチャを使うsuspend関数
    は、ディスパッチャを書き換えられるようにして
    おくのが望ましい

    View Slide

  213. 213
    間接的にコルーチンを起動する関数
    © 2019 Ubie, Inc.
    @RunWith(AndroidJUnit4::class)
    class ReadingHistoryViewModelTest {
    val app by lazy { ApplicationProvider.getApplicationContext() }
    val database by lazy {
    Room.databaseBuilder(app, Database::class.java, "database")
    .allowMainThreadQueries()
    .setQueryExecutor { it.run() }
    .build()
    }
    @Test
    fun insertTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(0)
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(database.readingHistoryDao().historyCount()).isEqualTo(1)
    }
    }
    OK

    View Slide

  214. 214
    コルーチンのテスト
    © 2019 Ubie, Inc.
    suspend関数をテストする runBlocking関数を使う
    間接的にコルーチンを起動する
    Yes
    No
    ↑に加えて
    runBlockingTest関数と
    setMain関数を使う
    メインセーフティで呼び出し、
    ディスパッチャーを差し替えるorモックする
    単純に完了を待つ
    実⾏タイミングを制御したい

    View Slide

  215. 215
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @Test
    fun viewStateTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  216. 216
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @Test
    fun viewStateTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    ViewStateの変化をテストする

    View Slide

  217. 217
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @Test
    fun viewStateTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    expected: Progress
    but was : Completed

    View Slide

  218. 218
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    コルーチンの実⾏タイミングを制御する

    View Slide

  219. 219
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    コルーチンの実⾏タイミングを制御する
    関数を呼び出したら最後までブロッキングで実⾏する

    View Slide

  220. 220
    © 2019 Ubie, Inc.
    class ReadingHistoryViewModel(private val database: Database) : ViewModel() {
    private val _viewState = MutableLiveData()
    val viewState: LiveData = _viewState
    fun addReadingHistory(history: RoomReadingHistory) {
    viewModelScope.launch {
    try {
    _viewState.value = ViewState.Progress
    database.readingHistoryDao().insert(history)
    _viewState.value = ViewState.Completed
    } catch (e: Exception) {
    _viewState.value = ViewState.Error(e)
    }
    }
    }
    }
    コルーチンの実⾏タイミングを制御する
    ⼀旦ここまで進めてアサーションして続きを進めたい

    View Slide

  221. 221
    © 2019 Ubie, Inc.
    class CoroutinesTestRule(
    val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
    ) : TestWatcher() {
    override fun starting(description: Description?) {
    super.starting(description)
    Dispatchers.setMain(testDispatcher)
    }
    override fun finished(description: Description?) {
    super.finished(description)
    Dispatchers.resetMain()
    testDispatcher.cleanupTestCoroutines()
    }
    }
    コルーチンの実⾏タイミングを制御する

    View Slide

  222. 222
    © 2019 Ubie, Inc.
    class CoroutinesTestRule(
    val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
    ) : TestWatcher() {
    override fun starting(description: Description?) {
    super.starting(description)
    Dispatchers.setMain(testDispatcher)
    }
    override fun finished(description: Description?) {
    super.finished(description)
    Dispatchers.resetMain()
    testDispatcher.cleanupTestCoroutines()
    }
    }
    コルーチンの実⾏タイミングを制御する
    経過時間を制御できるディスパッチャ

    View Slide

  223. 223
    © 2019 Ubie, Inc.
    class CoroutinesTestRule(
    val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
    ) : TestWatcher() {
    override fun starting(description: Description?) {
    super.starting(description)
    Dispatchers.setMain(testDispatcher)
    }
    override fun finished(description: Description?) {
    super.finished(description)
    Dispatchers.resetMain()
    testDispatcher.cleanupTestCoroutines()
    }
    }
    コルーチンの実⾏タイミングを制御する
    テスト開始時にMainディスパッチャー
    を書き換える

    View Slide

  224. 224
    © 2019 Ubie, Inc.
    class CoroutinesTestRule(
    val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
    ) : TestWatcher() {
    override fun starting(description: Description?) {
    super.starting(description)
    Dispatchers.setMain(testDispatcher)
    }
    override fun finished(description: Description?) {
    super.finished(description)
    Dispatchers.resetMain()
    testDispatcher.cleanupTestCoroutines()
    }
    }
    コルーチンの実⾏タイミングを制御する
    テスト完了時の後処理

    View Slide

  225. 225
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @Test
    fun viewStateTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  226. 226
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = runBlocking {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  227. 227
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  228. 228
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  229. 229
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    } expected: Progress
    but was : Completed
    "

    View Slide

  230. 230
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  231. 231
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    Roomデータベースをモックする

    View Slide

  232. 232
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    テスト内で使われるDAOや関数をモックする

    View Slide

  233. 233
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    任意の時間待たせる

    View Slide

  234. 234
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    この時点ではReadingHistoryDao.insertを実⾏中

    View Slide

  235. 235
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    advanceTimeBy(200)
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }

    View Slide

  236. 236
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    advanceTimeBy(200)
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    テストディスパッチャの時間を200ms進める関数

    View Slide

  237. 237
    コルーチンの実⾏タイミングを制御する
    © 2019 Ubie, Inc.
    @get:Rule
    val rule = CoroutinesTestRule()
    @Test
    fun viewStateTest() = rule.testDispatcher.runBlockingTest {
    val readingHistoryDao: ReadingHistoryDao = mockk {
    coEvery { insert(any()) } coAnswers {
    delay(100)
    Unit
    }
    }
    val database: Database = mockk {
    every { readingHistoryDao() } returns readingHistoryDao
    }
    val viewModel = ReadingHistoryViewModel(database)
    assertThat(viewModel.viewState.value).isNull()
    viewModel.addReadingHistory(createDummyReadingHistory(1))
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Progress)
    // ॲཧ͕׬ྃޙ
    advanceTimeBy(200)
    assertThat(viewModel.viewState.value).isEqualTo(ReadingHistoryViewModel.ViewState.Completed)
    }
    OK

    View Slide

  238. 238
    コルーチンのテストのまとめ
    © 2019 Ubie, Inc.
    suspend関数をテストする runBlocking関数を使う
    間接的にコルーチンを起動する
    Yes
    No
    ↑に加えて
    runBlockingTest関数と
    setMain関数を使う
    メインセーフティで呼び出し、
    ディスパッチャーを差し替えるorモックする
    単純に完了を待つ
    実⾏タイミングを制御したい

    View Slide

  239. 239
    今⽇話したこと
    © 2019 Ubie, Inc.
    • コルーチンとはなにか、なにがうれしいのか
    • Kotlinにおけるコルーチンの仕組み
    • Kotlinコルーチンのきほん
    • コルーチンスコープと構造化された並⾏性
    • コルーチンと設計
    • コルーチンのテスト

    View Slide

  240. 240
    本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂本⽂
    © 2019 Ubie, Inc.
    We are hiring!!

    View Slide

  241. 241
    ありがとうございました
    © 2019 Ubie, Inc.

    View Slide