Slide 1

Slide 1 text

Hadi Tok Android GDE @hadi_tok Android Threading

Slide 2

Slide 2 text

Basics

Slide 3

Slide 3 text

Threads A thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.

Slide 4

Slide 4 text

Threads A thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.

Slide 5

Slide 5 text

Threads A thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.

Slide 6

Slide 6 text

Threads A thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler, which is typically a part of the operating system.

Slide 7

Slide 7 text

public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { System.out.println("this is a thread"); } }; thread.start(); }

Slide 8

Slide 8 text

fun main() { val thread = Thread { print("this is a thread") } thread.start() }

Slide 9

Slide 9 text

Threads They are relatively expensive in java.

Slide 10

Slide 10 text

Threads They are relatively expensive in java. Shouldn’t need to create threads for every sequence of instructions we run.

Slide 11

Slide 11 text

Threads They are relatively expensive in java. Shouldn’t need to create threads for every sequence of instructions we run. To prevent this we have …

Slide 12

Slide 12 text

Runnables

Slide 13

Slide 13 text

Runnables Interface to execute code on a thread.

Slide 14

Slide 14 text

fun main() { val thread = Thread(Runnable{ print("this is a thread") }) thread.start() }

Slide 15

Slide 15 text

Runnables • A thread can run multiple runnables

Slide 16

Slide 16 text

Runnables • A thread can run multiple runnables • The runnables can be provided from the same thread or could be provided from external threads.

Slide 17

Slide 17 text

Runnables • A thread can run multiple runnables • The runnables can be provided from the same thread or could be provided from external threads. • This is not provided out of the box.

Slide 18

Slide 18 text

Main thread

Slide 19

Slide 19 text

What is main thread? it is in charge of dispatching events to the appropriate user interface widgets, including drawing events. It is also almost always the thread in which your application interacts with components from the Android UI toolkit (components from the android.widget and android.view packages)

Slide 20

Slide 20 text

Runnables • A thread can run multiple runnables • The runnables can be provided from the same thread or could be provided from external threads. • This is not provided out of the box.

Slide 21

Slide 21 text

Runnables • Main thread can run multiple runnables • The runnables can be provided from the main thread or could be provided from background threads. • This is not provided out of the box.

Slide 22

Slide 22 text

MessageQueue

Slide 23

Slide 23 text

What is MessageQueue What is MessageQueue?

Slide 24

Slide 24 text

MessageQueue Low-level class holding the list of messages to be dispatched by a Looper

Slide 25

Slide 25 text

MessageQueue Low-level class holding the list of messages to be dispatched by a Looper 🤔

Slide 26

Slide 26 text

Looper

Slide 27

Slide 27 text

What is Looper Class used to run a message loop for a thread.

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

for ( ;; ) { Message msg = queue.next(); // might block … msg.target.dispatchMessage(msg); … msg.recycleUnchecked(); }

Slide 31

Slide 31 text

public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }

Slide 32

Slide 32 text

private static void handleCallback(Message message) { message.callback.run(); }

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

MessageQueue Low-level class holding the list of messages to be dispatched by a Looper.

Slide 35

Slide 35 text

MessageQueue Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.

Slide 36

Slide 36 text

MessageQueue Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper. 🤔

Slide 37

Slide 37 text

Handler

Slide 38

Slide 38 text

What is Handler A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue

Slide 39

Slide 39 text

Main uses for handler • to enqueue an action to be performed on a different thread than your own

Slide 40

Slide 40 text

RunnableX Runnable3 MessageA MessageQueue

Slide 41

Slide 41 text

RunnableX Runnable3 MessageA MessageQueue Looper

Slide 42

Slide 42 text

RunnableX Runnable3 MessageA MessageQueue Handler MessageN Looper

Slide 43

Slide 43 text

RunnableX Runnable3 MessageA MessageQueue Handler MessageN Looper

Slide 44

Slide 44 text

RunnableX Runnable3 MessageA MessageQueue Looper Handler MessageN

Slide 45

Slide 45 text

Main uses for handler • to enqueue an action to be performed on a different thread than your own • to schedule messages and runnables to be executed at some point in the future

Slide 46

Slide 46 text

post()

Slide 47

Slide 47 text

post() Causes the Runnable r to be added to the message queue.

Slide 48

Slide 48 text

postAtFrontOfQueue()

Slide 49

Slide 49 text

postAtFrontOfQueue() Posts a message to an object that implements Runnable.

Slide 50

Slide 50 text

postAtFrontOfQueue() Posts a message to an object that implements Runnable. This method is only for use in very special circumstances -- it can easily starve the message queue, cause ordering problems, or have other unexpected side-effects.

Slide 51

Slide 51 text

postDelayed()

Slide 52

Slide 52 text

postDelayed() Causes the Runnable r to be added to the message queue, to be run after the speci fi ed amount of time elapses. The time-base is SystemClock.uptimeMillis()

Slide 53

Slide 53 text

postAtTime()

Slide 54

Slide 54 text

postAtTime() Causes the Runnable r to be added to the message queue, to be run at a speci fi c time given by uptimeMillis. The time-base is SystemClock.uptimeMillis()

Slide 55

Slide 55 text

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById(R.id.text_view) Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } }

Slide 56

Slide 56 text

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById(R.id.text_view) Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } }

Slide 57

Slide 57 text

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById(R.id.text_view) Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } 🤔

Slide 58

Slide 58 text

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById(R.id.text_view) textView.postDelayed(100){ Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } }

Slide 59

Slide 59 text

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById(R.id.text_view) textView.postDelayed(100){ Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } }

Slide 60

Slide 60 text

AsyncTask

Slide 61

Slide 61 text

AsyncTask

Slide 62

Slide 62 text

Executors

Slide 63

Slide 63 text

Executor An object that executes submitted Runnable tasks.

Slide 64

Slide 64 text

fun main() { val thread = Thread(Runnable{ print("this is a thread") }) thread.start() }

Slide 65

Slide 65 text

fun main() { executor.execute(Runnable{ print("this is a thread") }) }

Slide 66

Slide 66 text

fun main() { executor.execute { print("this is a thread") } }

Slide 67

Slide 67 text

val executor: ExecutorService = Executors.newFixedThreadPool(4)

Slide 68

Slide 68 text

ExecutorService An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks.

Slide 69

Slide 69 text

ExecutorService An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks.

Slide 70

Slide 70 text

ExecutorService An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks.

Slide 71

Slide 71 text

Future A Future represents the result of an asynchronous computation.

Slide 72

Slide 72 text

Future A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete

Slide 73

Slide 73 text

Future A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion,

Slide 74

Slide 74 text

Future A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.

Slide 75

Slide 75 text

Future The result can only be retrieved using method get

Slide 76

Slide 76 text

Future The result can only be retrieved using method get when the computation has completed,

Slide 77

Slide 77 text

Future The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready.

Slide 78

Slide 78 text

fun waitAndToastRandomInt() { val futureTask = FutureTask { val randomInt = Random.nextInt(10) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt } executor.submit(futureTask) Toast.makeText(this, "lucky number is ${futureTask.get()}” , Toast.LENGTH_LONG ).show() }

Slide 79

Slide 79 text

fun waitAndToastRandomInt() { val futureTask = FutureTask { val randomInt = Random.nextInt(10) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt } executor.submit(futureTask) Toast.makeText(this, "lucky number is ${futureTask.get()}” , Toast.LENGTH_LONG ).show() }

Slide 80

Slide 80 text

fun waitAndToastRandomInt() { val listenableFuture = SettableFuture.create() executor.submit { val randomInt = Random.nextInt(10) Thread.sleep(randomInt * 1000L) listenableFuture.set(randomInt) } listenableFuture.addListener({ Toast.makeText(this, "lucky number is ${listenableFuture.get()}", Toast.LENGTH_LONG ).show() }, ContextCompat.getMainExecutor(this))

Slide 81

Slide 81 text

fun waitAndToastRandomInt() { val listenableFuture = SettableFuture.create() executor.submit { val futureTask1 = (FutureTask { val randomInt = Random.nextInt(5) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt }) val futureTask2 = FutureTask { val randomInt = Random.nextInt(5) Thread.sleep(randomInt * 1000L) return@FutureTask randomInt } executor.submit(futureTask1) executor.submit(futureTask2) listenableFuture.set(futureTask1.get() + futureTask2.get()) } listenableFuture.addListener({ Toast.makeText(this, "lucky number is ${listenableFuture.get()}", Toast.LENGTH_LONG ).show() }, ContextCompat.getMainExecutor(this)) }

Slide 82

Slide 82 text

Thread Safety

Slide 83

Slide 83 text

fun threadSafety() { val list = mutableListOf(1) executor.submit { Thread.sleep(Random.nextLong(100)) if (list.isEmpty().not()) { list.remove(0); } } executor.submit { Thread.sleep(Random.nextLong(100)) if (list.isEmpty().not()) { list.remove(0); } } }

Slide 84

Slide 84 text

fun immutability() { var list = listOf(1, 2, 3) executor.submit { Thread.sleep(Random.nextLong(100)) val newList = list.toMutableList() if (newList.isEmpty().not()) { newList.remove(0) list = newList.toList() } } executor.submit { Thread.sleep(Random.nextLong(100)) val newList = list.toMutableList() if (newList.isEmpty().not()) { newList.remove(0) list = newList.toList() } } }

Slide 85

Slide 85 text

fun syncronizedCollections() { val list = mutableListOf() executor.submit { Thread.sleep(Random.nextLong(100)) list.addAll(listOf(10,20,30)) } executor.submit { Thread.sleep(Random.nextLong(100)) list.addAll(listOf(50,60,70)) } }

Slide 86

Slide 86 text

fun syncronizedCollections() { val list = Collections.synchronizedList(mutableListOf()) executor.submit { Thread.sleep(Random.nextLong(100)) list.addAll(listOf(10,20,30)) } executor.submit { Thread.sleep(Random.nextLong(100)) list.addAll(listOf(50,60,70)) } }

Slide 87

Slide 87 text

private val list = mutableListOf() fun syncronizedFunctions() { executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } fun addAllToList(items:List){ items.forEach { list.add(it) } }

Slide 88

Slide 88 text

private val list = mutableListOf() fun syncronizedFunctions() { executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } @Synchronized fun addAllToList(items:List){ items.forEach { list.add(it) } }

Slide 89

Slide 89 text

private val list = mutableListOf() fun syncronizedBlocks() { executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } fun addAllToList(items:List){ items.forEach { list.add(it) } }

Slide 90

Slide 90 text

private val list = mutableListOf() fun syncronizedBlocks() { executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(10, 20, 30)) } executor.submit { Thread.sleep(Random.nextLong(100)) addAllToList(listOf(50, 60, 70)) } } fun addAllToList(items: List) { synchronized(list) { items.forEach { list.add(it) } } Thread.sleep(Random.nextLong(100)) print(“asd”) }

Slide 91

Slide 91 text

Thank You! Hadi Tok Android GDE @hadi_tok

Slide 92

Slide 92 text

Hadi Tok Android GDE @hadi_tok Android Threading II

Slide 93

Slide 93 text

Coroutines

Slide 94

Slide 94 text

Long running tasks • Network requests • Database operations • Image processing • ...

Slide 95

Slide 95 text

Long running tasks • Challenge 1: Execute on a non-UI thread

Slide 96

Slide 96 text

Long running tasks • Challenge 1: Execute on a non-UI thread • Challenge 2: Get informed when the task is done

Slide 97

Slide 97 text

fun login(username: String, pass: String) { Thread { requestLogin(username, pass) { result -> showResult(result) } }.run() }

Slide 98

Slide 98 text

fun login(username: String, pass: String) { Thread { requestLogin(username, pass) { result -> Handler(Looper.getMainLooper()).post { showResult(result) } } }.run() }

Slide 99

Slide 99 text

fun showResult(result: Result) { when (result) { is Result.Success -> { Toast.makeText( this, "Login Success", Toast.LENGTH_SHORT ).show() } is Result.Failure -> { Log.d("TAG", result.error.message, result.error) Toast.makeText( this, "Login failure ${result.error.message}", Toast.LENGTH_SHORT ).show() } } }

Slide 100

Slide 100 text

fun login(username: String, pass: String) { requestLogin(username, pass) { result -> showResult(result) } }

Slide 101

Slide 101 text

onDraw onDraw onDraw requestLogin UI Thread fun login(name, pass) { requestLogin(name, pass) { result -> show(result) } }

Slide 102

Slide 102 text

onDraw onDraw onDraw requestLogin UI Thread fun login(name, pass) { requestLogin(name, pass) { result -> show(result) } }

Slide 103

Slide 103 text

onDraw onDraw onDraw requestLogin UI Thread show onDraw fun login(name, pass) { requestLogin(name, pass) { result -> show(result) } }

Slide 104

Slide 104 text

onDraw onDraw onDraw requestLogin UI Thread show onDraw fun login(name, pass) { requestLogin(name, pass) { result -> show(result) } } onDraw onDraw onDraw

Slide 105

Slide 105 text

Coroutine = suspendable computation onDraw onDraw onDraw requestLogin UI Thread show onDraw onDraw onDraw onDraw

Slide 106

Slide 106 text

Coroutine Write asynchronous code sequentially

Slide 107

Slide 107 text

fun login(username: String, pass: String) = GlobalScope.launch { val result = requestLogin(username, pass) showResult(result) }

Slide 108

Slide 108 text

fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.IO) { val result = requestLogin(username, pass) withContext(Dispatchers.Main) { showResult(result) } }

Slide 109

Slide 109 text

fun login(name, pass) { requestLogin(name, pass) { result -> show(result) } } requestLogin Thread show Suspension point

Slide 110

Slide 110 text

suspend fun requestLogin(username: String, pass: String): Result { delay(1000) // long running operation return Result.Success }

Slide 111

Slide 111 text

suspend fun requestLogin(username: String, pass: String): Result { // delay(1000) // long running operation return Result.Success }

Slide 112

Slide 112 text

suspend fun requestLogin(username: String, pass: String) : Result = withContext(Dispatchers.IO) { delay(1000) // long running operation return@withContext Result.Success }

Slide 113

Slide 113 text

fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val result = requestLogin(username, pass) showResult(result) }

Slide 114

Slide 114 text

fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token = auth(username, pass) val result = requestLogin(token) showResult(result) }

Slide 115

Slide 115 text

Dispatchers.Main ● Main thread ● Use it for: UI operations

Slide 116

Slide 116 text

Dispatchers.Default ● Common pool of shared background threads ● Use it for: computing-intensive coroutines Dispatchers.Main ● Main thread ● Use it for: UI operations

Slide 117

Slide 117 text

Dispatchers.Default ● Common pool of shared background threads ● Use it for: computing-intensive coroutines Dispatchers.IO ● Shared pool of on-demand created threads ● Use it for: IO-intensive blocking operations Dispatchers.Main ● Main thread ● Use it for: UI operations

Slide 118

Slide 118 text

Dispatchers.Default ● Common pool of shared background threads ● Use it for: computing-intensive coroutines Dispatchers.Unconfined ● Doesn’t confine the coroutine to any specific thread ● Don’t use it in code Dispatchers.IO ● Shared pool of on-demand created threads ● Use it for: IO-intensive blocking operations Dispatchers.Main ● Main thread ● Use it for: UI operations

Slide 119

Slide 119 text

fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token = auth(username, pass) val result = requestLogin(token) showResult(result) }

Slide 120

Slide 120 text

Error Handling Try Catch just works!

Slide 121

Slide 121 text

try { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } catch (e: Exception) { }

Slide 122

Slide 122 text

Launching and cancelling coroutines Login button pressed Launch coroutine requestLogin Show login status

Slide 123

Slide 123 text

Launching and cancelling coroutines Login button pressed Launch coroutine requestLogin Show login status Activity destroyed

Slide 124

Slide 124 text

Launching and cancelling coroutines Login button pressed Launch coroutine requestLogin Show login status ViewModel

Slide 125

Slide 125 text

val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job)

Slide 126

Slide 126 text

fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token = auth(username, pass) val result = requestLogin(token) showResult(result) }

Slide 127

Slide 127 text

fun login(username: String, pass: String) = uiScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) }

Slide 128

Slide 128 text

val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job) fun login(username: String, pass: String) = uiScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } override fun onCleared() { super.onCleared() job.cancel() }

Slide 129

Slide 129 text

val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job) fun login(username: String, pass: String) = uiScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } override fun onCleared() { super.onCleared() job.cancel() } lifecycle-viewmodel-ktx

Slide 130

Slide 130 text

val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job) fun login(username: String, pass: String) = viewModelScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) } override fun onCleared() { super.onCleared() job.cancel() }

Slide 131

Slide 131 text

fun login(username: String, pass: String) = viewModelScope.launch { val token = auth(username, pass) val result = requestLogin(token) showResult(result) }

Slide 132

Slide 132 text

How to make coroutines cancellable?

Slide 133

Slide 133 text

val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i < 5) { // computation loop, just wastes CPU // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i ++ } ... ") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")

Slide 134

Slide 134 text

job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm sleeping 3 ... job: I'm sleeping 4 ... main: Now I can quit.

Slide 135

Slide 135 text

val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (isActive) { // cancellable computation loop // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i ++ } ... ") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")

Slide 136

Slide 136 text

job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit.

Slide 137

Slide 137 text

val job = launch { try { repeat(1000) { i -> println("job: I'm sleeping $i ... ") delay(500L) } } finally { println("job: I'm running finally") } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.")

Slide 138

Slide 138 text

Composing Suspending functions

Slide 139

Slide 139 text

suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // pretend we are doing something useful here, too return 29 }

Slide 140

Slide 140 text

val time = measureTimeMillis { val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms”)

Slide 141

Slide 141 text

val time = measureTimeMillis { val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms”) // The answer is 42 // Completed in 2007 ms

Slide 142

Slide 142 text

val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")

Slide 143

Slide 143 text

val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms”) // The answer is 42 // Completed in 1017 ms

Slide 144

Slide 144 text

val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")

Slide 145

Slide 145 text

val time = measureTimeMillis { val one = async { doSomethingUsefulOne() }.await() val two = async { doSomethingUsefulTwo() }.await() println("The answer is ${one + two}") } println("Completed in $time ms")

Slide 146

Slide 146 text

val time = measureTimeMillis { val one = async { doSomethingUsefulOne() }.await() val two = async { doSomethingUsefulTwo() }.await() println("The answer is ${one + two}") } println("Completed in $time ms")

Slide 147

Slide 147 text

val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")

Slide 148

Slide 148 text

Observable Pattern

Slide 149

Slide 149 text

Observer pattern A software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

Slide 150

Slide 150 text

Observer pattern It is mainly used for implementing distributed event handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",

Slide 151

Slide 151 text

Observer pattern It is mainly used for implementing distributed event handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",

Slide 152

Slide 152 text

Observer pattern It is mainly used for implementing distributed event handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",

Slide 153

Slide 153 text

Observer pattern It is mainly used for implementing distributed event handling systems, in "event driven" software. In those systems, the subject is usually named a "stream of events" or "stream source of events",

Slide 154

Slide 154 text

Flow

Slide 155

Slide 155 text

Flow Using the List result type, means we can only return all the values at once. To represent the stream of values that are being asynchronously computed, we can use a Flow type just like we would use the Sequence type for synchronously computed values

Slide 156

Slide 156 text

fun simple(): Flow = flow { // flow builder for (i in 1 .. 3) { delay(100) // pretend we are doing something useful here emit(i) // emit next value } }

Slide 157

Slide 157 text

fun main() = runBlocking { simple().collect { value -> println(value) } }

Slide 158

Slide 158 text

fun main() = runBlocking { / / Launch a concurrent coroutine to check if the main thread is blocked launch { for (k in 1 .. 3) { println("I'm not blocked $k") delay(100) } } / / Collect the flow simple().collect { value -> println(value) } } fun simple(): Flow = flow { / / flow builder for (i in 1 . . 3) { delay(100) // pretend we are doing something useful here emit(i) / / emit next value } }

Slide 159

Slide 159 text

I'm not blocked 1 1 I'm not blocked 2 2 I'm not blocked 3 3

Slide 160

Slide 160 text

Flows are cold Flows are cold streams similar to sequences — the code inside a flow builder does not run until the flow is collected. This becomes clear in the following example:

Slide 161

Slide 161 text

fun simple(): Flow = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } } fun main() = runBlocking { println("Calling simple function ... ") val flow = simple() println("Calling collect ... ") flow.collect { value -> println(value) } println("Calling collect again ... ") flow.collect { value -> println(value) } }

Slide 162

Slide 162 text

Calling simple function ... Calling collect ... Flow started 1 2 3 Calling collect again ... Flow started 1 2 3

Slide 163

Slide 163 text

fun simple(): Flow = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } } fun main() = runBlocking { println("Calling simple function ... ") val flow = simple() println("Calling collect…”) flow.collect { value -> println(value) } println("Calling collect again ... ") flow.collect { value -> println(value) } }

Slide 164

Slide 164 text

Calling simple function ... Calling collect ... Flow started 1 2 3 Calling collect again ... Flow started 1 2 3

Slide 165

Slide 165 text

Flows are cold This is a key reason the simple function (which returns a flow) is not marked with suspend modifier. By itself, simple() call returns quickly and does not wait for anything. The flow starts every time it is collected, that is why we see "Flow started" when we call collect again.

Slide 166

Slide 166 text

Operators

Slide 167

Slide 167 text

suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running asynchronous work return "response $request" } fun main() = runBlocking { (1 .. 3).asFlow() // a flow of requests .map { request -> performRequest(request) } .collect { response -> println(response) } }

Slide 168

Slide 168 text

suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running asynchronous work return "response $request" } fun main() = runBlocking { (1 .. 3).asFlow() // a flow of requests .map { request -> performRequest(request) } .collect { response -> println(response) } } response 1 response 2 response 3

Slide 169

Slide 169 text

suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running asynchronous work return "response $request" } fun main() = runBlocking { (1 .. 3).asFlow() // a flow of requests .transform { request -> emit("Making request $request") emit(performRequest(request)) } .collect { response -> println(response) } }

Slide 170

Slide 170 text

suspend fun performRequest(request: Int): String { delay(1000) / / imitate long-running asynchronous work return "response $request" } fun main() = runBlocking { (1 . . 3).asFlow() // a flow of requests .transform { request -> emit("Making request $request") emit(performRequest(request)) } .collect { response - > println(response) } } Making request 1 response 1 Making request 2 response 2 Making request 3 response 3

Slide 171

Slide 171 text

fun main() = runBlocking { (1 .. 3).asFlow() // a flow of requests .take(2) .collect { value -> println(value) } }

Slide 172

Slide 172 text

fun main() = runBlocking { (1 .. 3).asFlow() // a flow of requests .take(2) .collect { value -> println(value) } } 1 2

Slide 173

Slide 173 text

Flow context

Slide 174

Slide 174 text

Flow context Collection of a flow always happens in the context of the calling coroutine

Slide 175

Slide 175 text

fun main() = runBlocking { // Collect the flow simple().collect { value -> println(value) } } fun simple(): Flow = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } }

Slide 176

Slide 176 text

fun main() = runBlocking { // Collect the flow simple().collect { value -> println(value) } } fun simple(): Flow = flow { kotlinx.coroutines.withContext(Dispatchers.Default) { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } } }

Slide 177

Slide 177 text

Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated: Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323], but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default]. Please refer to 'flow' documentation or use 'flowOn' instead at ...

Slide 178

Slide 178 text

fun main() = runBlocking { // Collect the flow simple().collect { value -> println(value) } } fun simple(): Flow = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } }.flowOn(Dispatchers.Default)

Slide 179

Slide 179 text

Thank You! Hadi Tok Android GDE @hadi_tok