Slide 1

Slide 1 text

Kotlin Coroutines & Android Kenji Abe / @STAR_ZERO Android, Kotlin GDE Cookpad Inc.

Slide 2

Slide 2 text

This class was deprecated in API level 30. Use the standard java.util.concurrent or Kotlin concurrency utilities instead. AsyncTask

Slide 3

Slide 3 text

Title safe Actio n safe Kotlin Coroutines are the recommended solution for async code.

Slide 4

Slide 4 text

Coroutines

Slide 5

Slide 5 text

fun request(callback: (Data) -> Unit) { // Network access } fun loadData() { request { data -> show(data) } } Callback

Slide 6

Slide 6 text

fun loadData() { request1 { data1 -> request2 { data2 -> request3 { data3 -> request4 { data4 -> request5 { data5 -> // ... } } } } } } Callback

Slide 7

Slide 7 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines

Slide 8

Slide 8 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines

Slide 9

Slide 9 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines

Slide 10

Slide 10 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines request show Background Thread

Slide 11

Slide 11 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines request show Background Thread

Slide 12

Slide 12 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines request show Background Thread 待機

Slide 13

Slide 13 text

suspend fun request(): String = withContext(Dispatchers.IO) { // Network access } suspend fun loadData() { val data = request() show(data) } Coroutines request show Background Thread

Slide 14

Slide 14 text

CoroutineDispatcher

Slide 15

Slide 15 text

CoroutineDispatcher Main IO Default UI Network & Disk CPU

Slide 16

Slide 16 text

suspend fun request(): String = withContext(Dispatchers.IO) { // ... } CoroutineDispatcher

Slide 17

Slide 17 text

suspend fun run() { withContext(Dispatchers.IO) { // ... } withContext(Dispatchers.Default) { // ... } withContext(Dispatchers.Main) { // ... } } CoroutineDispatcher

Slide 18

Slide 18 text

CoroutineScope / CoroutineBuilder

Slide 19

Slide 19 text

० CoroutineScope ○ Coroutineのライフサイクルの管理 ○ キャンセルや例外処理の対応 ० CoroutineBuilder ○ 新しくCoroutineを作成して実行する ○ launch / async CoroutineScope / CoroutineBuilder

Slide 20

Slide 20 text

fun event() { request() } suspend fun request() { // ... } CoroutineScope / CoroutineBuilder

Slide 21

Slide 21 text

fun event() { request() } suspend fun request() { // ... } CoroutineScope / CoroutineBuilder Suspend function 'request' should be called only from a coroutine or another suspend function

Slide 22

Slide 22 text

val scope = CoroutineScope(Dispatchers.Main) fun event() { scope.launch { request() } } CoroutineScope / CoroutineBuilder

Slide 23

Slide 23 text

val scope = CoroutineScope(Dispatchers.Main) fun event() { scope.launch { request() } } CoroutineScope / CoroutineBuilder CoroutineScope 作成

Slide 24

Slide 24 text

val scope = CoroutineScope(Dispatchers.Main) fun event() { scope.launch { request() } } CoroutineScope / CoroutineBuilder Coroutine 作成

Slide 25

Slide 25 text

Cancellation

Slide 26

Slide 26 text

val job = scope.launch { request() } // ... job.cancel() // or scope.cancel() Cancellation

Slide 27

Slide 27 text

val job = scope.launch { while(true) { // ... println("Hello") } } // ... job.cancel() Cancellation

Slide 28

Slide 28 text

val job = scope.launch { while(true) { // ... println("Hello") } } // ... job.cancel() Cancellation Hello Hello Hello Hello Hello Hello . .

Slide 29

Slide 29 text

val job = scope.launch { while(isActive) { // ... println("Hello") } } // ... job.cancel() Cancellation キャンセルされているか確認

Slide 30

Slide 30 text

val job = scope.launch { while(isActive) { ensureActive() // ... println("Hello") } } // ... job.cancel() Cancellation キャンセルされている場合 CancellationException

Slide 31

Slide 31 text

async / await

Slide 32

Slide 32 text

suspend fun request(): Data { delay(3000) // ... } val time = measureTimeMillis { val data1 = request() val data2 = request() } println("time = $time") async / await

Slide 33

Slide 33 text

suspend fun request(): Data { delay(3000) // ... } val time = measureTimeMillis { val data1 = request() val data2 = request() } println("time = $time") async / await 約 6000 ms

Slide 34

Slide 34 text

val time = measureTimeMillis { val deferred1 = async { request() } val deferred2 = async { request() } val data1 = deferred1.await() val data2 = deferred2.await() } println("time = $time") async / await

Slide 35

Slide 35 text

val time = measureTimeMillis { val deferred1 = async { request() } val deferred2 = async { request() } val data1 = deferred1.await() val data2 = deferred2.await() } println("time = $time") async / await 処理開始して待たずに次へ

Slide 36

Slide 36 text

val time = measureTimeMillis { val deferred1 = async { request() } val deferred2 = async { request() } val data1 = deferred1.await() val data2 = deferred2.await() } println("time = $time") async / await 完了を待って結果を返す

Slide 37

Slide 37 text

val time = measureTimeMillis { val deferred1 = async { request() } val deferred2 = async { request() } val data1 = deferred1.await() val data2 = deferred2.await() } println("time = $time") async / await 約 3000 ms

Slide 38

Slide 38 text

Exception

Slide 39

Slide 39 text

val scope = CoroutineScope(Dispatchers.Default) scope.launch { throw IllegalStateException() } Exception ❌

Slide 40

Slide 40 text

val deferred = async { throw IllegalStateException() } deferred.await() Exception ❌

Slide 41

Slide 41 text

val handler = CoroutineExceptionHandler { _, throwable -> // ... } val scope = CoroutineScope(Dispatchers.Default + handler) scope.launch { throw IllegalStateException() } Exception

Slide 42

Slide 42 text

val handler = CoroutineExceptionHandler { _, throwable -> // ... } val scope = CoroutineScope(Dispatchers.Default + handler) scope.launch { throw IllegalStateException() } Exception

Slide 43

Slide 43 text

Flow

Slide 44

Slide 44 text

० 複数の値 ० Cold Stream (Terminal operatorsで処理を開始) ० 様々なオペレーター Flow

Slide 45

Slide 45 text

fun createFlow(): Flow { return flow { (1..10).forEach { emit(it) } } } scope.launch { val flow = createFlow() flow.collect { println(it) } } Flow

Slide 46

Slide 46 text

fun createFlow(): Flow { return flow { (1..10).forEach { emit(it) } } } scope.launch { val flow = createFlow() flow.collect { println(it) } } Flow Flowを作成

Slide 47

Slide 47 text

fun createFlow(): Flow { return flow { (1..10).forEach { emit(it) } } } scope.launch { val flow = createFlow() flow.collect { println(it) } } Flow Flowはまだ動かない

Slide 48

Slide 48 text

fun createFlow(): Flow { return flow { (1..10).forEach { emit(it) } } } scope.launch { val flow = createFlow() flow.collect { println(it) } } Flow ここで動きだす

Slide 49

Slide 49 text

fun createFlow(): Flow { return flow { (1..10).forEach { emit(it) } } } scope.launch { val flow = createFlow() flow.collect { println(it) } } Flow 値を送信 emit された値が通知される

Slide 50

Slide 50 text

flow.map { it * it }.filter { it % 3 == 0 }.collect { // ... } Flow

Slide 51

Slide 51 text

suspendCancellableCoroutine callbackFlow

Slide 52

Slide 52 text

suspend fun run() = suspendCancellableCoroutine { continuation -> request(object : Callback { override fun success(response: Response) { continuation.resume(response) } override fun failure(e: Exception) { continuation.resumeWithException(e) } }) continuation.invokeOnCancellation { cancel() } } suspendCancellableCoroutine

Slide 53

Slide 53 text

suspend fun run() = suspendCancellableCoroutine { continuation -> request(object : Callback { override fun success(response: Response) { continuation.resume(response) } override fun failure(e: Exception) { continuation.resumeWithException(e) } }) continuation.invokeOnCancellation { cancel() } } suspendCancellableCoroutine

Slide 54

Slide 54 text

suspend fun run() = suspendCancellableCoroutine { continuation -> request(object : Callback { override fun success(response: Response) { continuation.resume(response) } override fun failure(e: Exception) { continuation.resumeWithException(e) } }) continuation.invokeOnCancellation { cancel() } } suspendCancellableCoroutine suspend funの戻り値

Slide 55

Slide 55 text

suspend fun run() = suspendCancellableCoroutine { continuation -> request(object : Callback { override fun success(response: Response) { continuation.resume(response) } override fun failure(e: Exception) { continuation.resumeWithException(e) } }) continuation.invokeOnCancellation { cancel() } } suspendCancellableCoroutine throw Exception

Slide 56

Slide 56 text

suspend fun run() = suspendCancellableCoroutine { continuation -> request(object : Callback { override fun success(response: Response) { continuation.resume(response) } override fun failure(e: Exception) { continuation.resumeWithException(e) } }) continuation.invokeOnCancellation { cancel() } } suspendCancellableCoroutine Coroutineがキャンセルされた

Slide 57

Slide 57 text

fun run() = callbackFlow { val callback = object : Callback { override fun onReceive(data: Data) { offer(data) } } registerCallback(callback) awaitClose { unregisterCallback(callback) } } callbackFlow

Slide 58

Slide 58 text

fun run() = callbackFlow { val callback = object : Callback { override fun onReceive(data: Data) { offer(data) } } registerCallback(callback) awaitClose { unregisterCallback(callback) } } callbackFlow

Slide 59

Slide 59 text

fun run() = callbackFlow { val callback = object : Callback { override fun onReceive(data: Data) { offer(data) } } registerCallback(callback) awaitClose { unregisterCallback(callback) } } callbackFlow 値を送信

Slide 60

Slide 60 text

fun run() = callbackFlow { val callback = object : Callback { override fun onReceive(data: Data) { offer(data) } } registerCallback(callback) awaitClose { unregisterCallback(callback) } } callbackFlow キャンセルされるまで待機

Slide 61

Slide 61 text

Coroutines Android

Slide 62

Slide 62 text

Room

Slide 63

Slide 63 text

@Dao interface UserDao { @Query("SELECT * FROM user") suspend fun getAll(): List } Room

Slide 64

Slide 64 text

@Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flow> } Room

Slide 65

Slide 65 text

WorkManager

Slide 66

Slide 66 text

class MyWorker( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val data = readData() upload(data) return Result.success() } } WorkManager

Slide 67

Slide 67 text

CoroutineScope

Slide 68

Slide 68 text

viewModelScope.launch { // ... } viewModelScope init Cancelled Active onCleared

Slide 69

Slide 69 text

lifecycleScope.launch { // ... } lifecycleScope onCreate onPause Cancelled Active onStart onResume onDestroy onStop init

Slide 70

Slide 70 text

lifecycleScope.launchWhenCreated { // ... } lifecycleScope onCreate onPause Cancelled Active onStart onResume onDestroy onStop init Pause

Slide 71

Slide 71 text

lifecycleScope.launchWhenStarted { // ... } lifecycleScope onCreate onPause Cancelled Pause onStart onResume onDestroy onStop init Active Pause

Slide 72

Slide 72 text

lifecycleScope.launchWhenResumed { // ... } lifecycleScope onCreate onPause onStart onResume onDestroy onStop init Cancelled Pause Active Pause

Slide 73

Slide 73 text

LiveData

Slide 74

Slide 74 text

val data: LiveData = liveData { // ... emit("test") } LiveData

Slide 75

Slide 75 text

val data: LiveData = liveData { // ... emit("test") } LiveData suspend

Slide 76

Slide 76 text

val data: LiveData = liveData { // ... emit("test") } LiveData 値を送信

Slide 77

Slide 77 text

val data = MutableLiveData(0) val flow = data.asFlow() scope.launch { flow.collect { // ... } } LiveData

Slide 78

Slide 78 text

val data = MutableLiveData(0) val flow = data.asFlow() scope.launch { flow.collect { // ... } } LiveData LiveData → Flow

Slide 79

Slide 79 text

val flow = flow { // ... emit("test") } val liveData = flow.asLiveData() liveData.observe(this) { // ... } LiveData

Slide 80

Slide 80 text

val flow = flow { // ... emit("test") } val liveData = flow.asLiveData() liveData.observe(this) { // ... } LiveData Flow → LiveData

Slide 81

Slide 81 text

asLiveData vs lifecycleScope.launchWhenStarted

Slide 82

Slide 82 text

fun FusedLocationProviderClient.locationFlow() = callbackFlow { val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult?) { result ?: return try { offer(result.lastLocation) } catch(e: Exception) {} } } requestLocationUpdates(/* ... */) .addOnFailureListener { e -> close(e) // in case of exception, close the Flow } // clean up when Flow collection ends awaitClose { removeLocationUpdates(callback) } } asLiveData vs lifecycleScope.launchWhenStarted

Slide 83

Slide 83 text

myFlow.asLiveData().observe { // ... } lifecycleScope.launchWhenStarted { myFlow.collect { // ... } } asLiveData vs lifecycleScope.launchWhenStarted

Slide 84

Slide 84 text

asLiveData vs lifecycleScope.launchWhenStarted Activity onStart Active Active onStop asLiveData lifecycleScope.launchWhenStarted Observer: Flow: Active Active Collector: Flow:

Slide 85

Slide 85 text

asLiveData vs lifecycleScope.launchWhenStarted Activity onStart Active Active onStop asLiveData lifecycleScope.launchWhenStarted Cancelled Cancelled Observer: Flow: Observer: Flow: Active Active Paused Active Collector: Flow: Collector: Flow:

Slide 86

Slide 86 text

And more...

Slide 87

Slide 87 text

New feature

Slide 88

Slide 88 text

StateFlow

Slide 89

Slide 89 text

class ViewModel { private val _counter = MutableStateFlow(0) val counter: StateFlow = _counter fun increment() { _counter.value++ } } lifecycleScope.launchWhenStarted { viewModel.counter.collect { println(it) } } StateFlow

Slide 90

Slide 90 text

० FlowのHot Stream ० 読み込み専用のStateFlow / 書込み可能なMutableStateFlow ० 様々なオペレーター StateFlow

Slide 91

Slide 91 text

MutableLiveData: var value: T fun postValue(value: T) LiveData: val value: T LiveData vs StateFlow MutableStateFlow: var value: T StateFlow: val value: T

Slide 92

Slide 92 text

LiveData vs StateFlow ● Data Holder ● LifecycleOwner ● 同じ値も通知される ● 初期値は通知されない ● Transformations ● Flow (Hot Stream) ● CoroutineScope ● 同じ値は通知されない ● 初期値も通知 ● オペレーター LiveData StateFlow

Slide 93

Slide 93 text

Appendix goo.gle/coroutines-posts ● Coroutines: First things first ● Cancellation in coroutines ● Exceptions in Coroutines ● The suspend modifier — Under the hood goo.gle/coroutines-101 ● Coroutines 101 codelabs.developers.google.com ● Use Kotlin Coroutines in your Android App ● Learn advanced coroutines with Kotlin Flow and LiveData ● Building a Kotlin extensions library

Slide 94

Slide 94 text

Thank you!