Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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,") }

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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を使ってキャンセルできる

Slide 96

Slide 96 text

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!

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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) } }

Slide 111

Slide 111 text

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}") }

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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}") } 通信エラー

Slide 114

Slide 114 text

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") } }

Slide 115

Slide 115 text

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で 囲む形で良い

Slide 116

Slide 116 text

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) } } } }

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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から呼び出す

Slide 119

Slide 119 text

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のコルーチンスコープ

Slide 120

Slide 120 text

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) } } } } メインスレッドで動作する

Slide 121

Slide 121 text

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で動作する

Slide 122

Slide 122 text

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) } } } }

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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() } }

Slide 126

Slide 126 text

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バウンドのスレッドプールで実⾏する

Slide 127

Slide 127 text

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() } }

Slide 128

Slide 128 text

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() } }

Slide 129

Slide 129 text

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を返し、コルーチンを実⾏する

Slide 130

Slide 130 text

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から結果を取り出す

Slide 131

Slide 131 text

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() } } 通信エラー

Slide 132

Slide 132 text

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() } } }

Slide 133

Slide 133 text

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() } } }

Slide 134

Slide 134 text

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() } } }

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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() } } }

Slide 138

Slide 138 text

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() } } }

Slide 139

Slide 139 text

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() } } } ⼦スコープを作る関数

Slide 140

Slide 140 text

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()

Slide 141

Slide 141 text

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() この範囲が処理の単位になる

Slide 142

Slide 142 text

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() この範囲が処理の単位になる

Slide 143

Slide 143 text

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()

Slide 144

Slide 144 text

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()

Slide 145

Slide 145 text

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()

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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コルーチンのサポートが 様々なプラットフォームで進んでおり、⾃前で ⽤意する必要はほとんどなくなった

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

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

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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)

Slide 183

Slide 183 text

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) アサーションとモックライブラリ

Slide 184

Slide 184 text

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

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

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) } }

Slide 187

Slide 187 text

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) } } ブロッキングでコルーチンを実⾏

Slide 188

Slide 188 text

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をモック

Slide 189

Slide 189 text

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関数を呼び出す

Slide 190

Slide 190 text

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) } } アサーション

Slide 191

Slide 191 text

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) } }

Slide 192

Slide 192 text

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()) } } }

Slide 193

Slide 193 text

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()) } } } 呼び出し元と異なるスコープでコルーチン を起動しない限りはこういう形などでも問 題ない

Slide 194

Slide 194 text

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

Slide 195

Slide 195 text

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) } } } }

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

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関数を宣⾔している

Slide 198

Slide 198 text

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) } } } } 通常の関数呼び出しからコルーチンを起動する

Slide 199

Slide 199 text

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関数を呼び出す

Slide 200

Slide 200 text

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) } }

Slide 201

Slide 201 text

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で動かす

Slide 202

Slide 202 text

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を初期化する

Slide 203

Slide 203 text

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で動かす

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

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) } } } }

Slide 206

Slide 206 text

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) } } } } テストではここまで到達して中断し、制御 が戻る(アサーションに進んでしまう)

Slide 207

Slide 207 text

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

Slide 208

Slide 208 text

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) } } } } この実⾏をテストスレッドで⾏い たい

Slide 209

Slide 209 text

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) } }

Slide 210

Slide 210 text

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) } }

Slide 211

Slide 211 text

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) } } テストスレッドで実⾏するようにする

Slide 212

Slide 212 text

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関数 は、ディスパッチャを書き換えられるようにして おくのが望ましい

Slide 213

Slide 213 text

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

Slide 214

Slide 214 text

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

Slide 215

Slide 215 text

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) }

Slide 216

Slide 216 text

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の変化をテストする

Slide 217

Slide 217 text

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

Slide 218

Slide 218 text

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) } } } } コルーチンの実⾏タイミングを制御する

Slide 219

Slide 219 text

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) } } } } コルーチンの実⾏タイミングを制御する 関数を呼び出したら最後までブロッキングで実⾏する

Slide 220

Slide 220 text

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) } } } } コルーチンの実⾏タイミングを制御する ⼀旦ここまで進めてアサーションして続きを進めたい

Slide 221

Slide 221 text

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() } } コルーチンの実⾏タイミングを制御する

Slide 222

Slide 222 text

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() } } コルーチンの実⾏タイミングを制御する 経過時間を制御できるディスパッチャ

Slide 223

Slide 223 text

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ディスパッチャー を書き換える

Slide 224

Slide 224 text

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() } } コルーチンの実⾏タイミングを制御する テスト完了時の後処理

Slide 225

Slide 225 text

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) }

Slide 226

Slide 226 text

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) }

Slide 227

Slide 227 text

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) }

Slide 228

Slide 228 text

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) }

Slide 229

Slide 229 text

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 "

Slide 230

Slide 230 text

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) }

Slide 231

Slide 231 text

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データベースをモックする

Slide 232

Slide 232 text

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や関数をモックする

Slide 233

Slide 233 text

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) } 任意の時間待たせる

Slide 234

Slide 234 text

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を実⾏中

Slide 235

Slide 235 text

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) }

Slide 236

Slide 236 text

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進める関数

Slide 237

Slide 237 text

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

Slide 238

Slide 238 text

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

Slide 239

Slide 239 text

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

Slide 240

Slide 240 text

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

Slide 241

Slide 241 text

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