Save 37% off PRO during our Black Friday Sale! »

Android Async Programming

A6c0dcba5f373b33df2c2c55540faab1?s=47 Hadi Tok
April 25, 2021
19

Android Async Programming

Presentation about Threading in Android. Topics : MessageQueue, Looper, Hander, Futures, Thread Safety, Cancellation, Coroutines, Flow.

A6c0dcba5f373b33df2c2c55540faab1?s=128

Hadi Tok

April 25, 2021
Tweet

Transcript

  1. Hadi Tok Android GDE @hadi_tok Android Threading

  2. Basics

  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. public static void main(String[] args) { Thread thread = new

    Thread() { @Override public void run() { System.out.println("this is a thread"); } }; thread.start(); }
  8. fun main() { val thread = Thread { print("this is

    a thread") } thread.start() }
  9. Threads They are relatively expensive in java.

  10. Threads They are relatively expensive in java. Shouldn’t need to

    create threads for every sequence of instructions we run.
  11. 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 …
  12. Runnables

  13. Runnables Interface to execute code on a thread.

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

    thread") }) thread.start() }
  15. Runnables • A thread can run multiple runnables

  16. Runnables • A thread can run multiple runnables • The

    runnables can be provided from the same thread or could be provided from external threads.
  17. 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.
  18. Main thread

  19. 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)
  20. 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.
  21. 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.
  22. MessageQueue

  23. What is MessageQueue What is MessageQueue?

  24. MessageQueue Low-level class holding the list of messages to be

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

    dispatched by a Looper 🤔
  26. Looper

  27. What is Looper Class used to run a message loop

    for a thread.
  28. None
  29. None
  30. for ( ;; ) { Message msg = queue.next(); //

    might block … msg.target.dispatchMessage(msg); … msg.recycleUnchecked(); }
  31. public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null)

    { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
  32. private static void handleCallback(Message message) { message.callback.run(); }

  33. None
  34. MessageQueue Low-level class holding the list of messages to be

    dispatched by a Looper.
  35. 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.
  36. 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. 🤔
  37. Handler

  38. What is Handler A Handler allows you to send and

    process Message and Runnable objects associated with a thread's MessageQueue
  39. Main uses for handler • to enqueue an action to

    be performed on a different thread than your own
  40. RunnableX Runnable3 MessageA MessageQueue

  41. RunnableX Runnable3 MessageA MessageQueue Looper

  42. RunnableX Runnable3 MessageA MessageQueue Handler MessageN Looper

  43. RunnableX Runnable3 MessageA MessageQueue Handler MessageN Looper

  44. RunnableX Runnable3 MessageA MessageQueue Looper Handler MessageN

  45. 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
  46. post()

  47. post() Causes the Runnable r to be added to the

    message queue.
  48. postAtFrontOfQueue()

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

  50. 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.
  51. postDelayed()

  52. 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()
  53. postAtTime()

  54. 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()
  55. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

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

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

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

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

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView =findViewById<TextView>(R.id.text_view) textView.postDelayed(100){ Log.d("MainAcivity", "onCreate: ${textView.width} ${textView.height}") } } }
  60. AsyncTask

  61. AsyncTask

  62. Executors

  63. Executor An object that executes submitted Runnable tasks.

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

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

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

    }
  67. val executor: ExecutorService = Executors.newFixedThreadPool(4)

  68. 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.
  69. 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.
  70. 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.
  71. Future A Future represents the result of an asynchronous computation.

  72. Future A Future represents the result of an asynchronous computation.

    Methods are provided to check if the computation is complete
  73. 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,
  74. 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.
  75. Future The result can only be retrieved using method get

  76. Future The result can only be retrieved using method get

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

    when the computation has completed, blocking if necessary until it is ready.
  78. 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() }
  79. 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() }
  80. fun waitAndToastRandomInt() { val listenableFuture = SettableFuture.create<Int>() 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))
  81. fun waitAndToastRandomInt() { val listenableFuture = SettableFuture.create<Int>() 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)) }
  82. Thread Safety

  83. 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); } } }
  84. 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() } } }
  85. fun syncronizedCollections() { val list = mutableListOf<Int>() 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)) } }
  86. fun syncronizedCollections() { val list = Collections.synchronizedList(mutableListOf<Int>()) 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)) } }
  87. private val list = mutableListOf<Int>() 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<Int>){ items.forEach { list.add(it) } }
  88. private val list = mutableListOf<Int>() 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<Int>){ items.forEach { list.add(it) } }
  89. private val list = mutableListOf<Int>() 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<Int>){ items.forEach { list.add(it) } }
  90. private val list = mutableListOf<Int>() 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<Int>) { synchronized(list) { items.forEach { list.add(it) } } Thread.sleep(Random.nextLong(100)) print(“asd”) }
  91. Thank You! Hadi Tok Android GDE @hadi_tok

  92. Hadi Tok Android GDE @hadi_tok Android Threading II

  93. Coroutines

  94. Long running tasks • Network requests • Database operations •

    Image processing • ...
  95. Long running tasks • Challenge 1: Execute on a non-UI

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

    thread • Challenge 2: Get informed when the task is done
  97. fun login(username: String, pass: String) { Thread { requestLogin(username, pass)

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

    { result -> Handler(Looper.getMainLooper()).post { showResult(result) } } }.run() }
  99. 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() } } }
  100. fun login(username: String, pass: String) { requestLogin(username, pass) { result

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

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

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

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

    pass) { requestLogin(name, pass) { result -> show(result) } } onDraw onDraw onDraw
  105. Coroutine = suspendable computation onDraw onDraw onDraw requestLogin UI Thread

    show onDraw onDraw onDraw onDraw
  106. Coroutine Write asynchronous code sequentially

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

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

    = requestLogin(username, pass) withContext(Dispatchers.Main) { showResult(result) } }
  109. fun login(name, pass) { requestLogin(name, pass) { result -> show(result)

    } } requestLogin Thread show Suspension point
  110. suspend fun requestLogin(username: String, pass: String): Result { delay(1000) //

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

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

    { delay(1000) // long running operation return@withContext Result.Success }
  113. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val result

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

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  115. Dispatchers.Main • Main thread • Use it for: UI operations

  116. Dispatchers.Default • Common pool of shared background threads • Use

    it for: computing-intensive coroutines Dispatchers.Main • Main thread • Use it for: UI operations
  117. 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
  118. 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
  119. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  120. Error Handling Try Catch just works!

  121. try { val token = auth(username, pass) val result =

    requestLogin(token) showResult(result) } catch (e: Exception) { }
  122. Launching and cancelling coroutines Login button pressed Launch coroutine requestLogin

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

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

    Show login status ViewModel
  125. val job = Job() val uiScope = CoroutineScope(Dispatchers.Main + job)

  126. fun login(username: String, pass: String) = GlobalScope.launch(Dispatchers.Main) { val token

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

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  128. 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() }
  129. 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
  130. 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() }
  131. fun login(username: String, pass: String) = viewModelScope.launch { val token

    = auth(username, pass) val result = requestLogin(token) showResult(result) }
  132. How to make coroutines cancellable?

  133. 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.")
  134. 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.
  135. 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.")
  136. 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.
  137. 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.")
  138. Composing Suspending functions

  139. 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 }
  140. val time = measureTimeMillis { val one = doSomethingUsefulOne() val

    two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms”)
  141. 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
  142. val time = measureTimeMillis { val one = async {

    doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms")
  143. 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
  144. val time = measureTimeMillis { val one = async {

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

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

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

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

  149. 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.
  150. 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",
  151. 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",
  152. 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",
  153. 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",
  154. Flow

  155. Flow Using the List<Int> 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<Int> type just like we would use the Sequence<Int> type for synchronously computed values
  156. fun simple(): Flow<Int> = flow { // flow builder for

    (i in 1 .. 3) { delay(100) // pretend we are doing something useful here emit(i) // emit next value } }
  157. fun main() = runBlocking { simple().collect { value -> println(value)

    } }
  158. fun main() = runBlocking<Unit> { / / 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<Int> = flow { / / flow builder for (i in 1 . . 3) { delay(100) // pretend we are doing something useful here emit(i) / / emit next value } }
  159. I'm not blocked 1 1 I'm not blocked 2 2

    I'm not blocked 3 3
  160. 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:
  161. fun simple(): Flow<Int> = flow { println("Flow started") for (i

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

    2 3 Calling collect again ... Flow started 1 2 3
  163. fun simple(): Flow<Int> = flow { println("Flow started") for (i

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

    2 3 Calling collect again ... Flow started 1 2 3
  165. 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.
  166. Operators

  167. suspend fun performRequest(request: Int): String { delay(1000) // imitate long-running

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

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

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

    long-running asynchronous work return "response $request" } fun main() = runBlocking<Unit> { (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
  171. fun main() = runBlocking<Unit> { (1 .. 3).asFlow() // a

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

    flow of requests .take(2) .collect { value -> println(value) } } 1 2
  173. Flow context

  174. Flow context Collection of a flow always happens in the

    context of the calling coroutine
  175. fun main() = runBlocking<Unit> { // Collect the flow simple().collect

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

    { value -> println(value) } } fun simple(): Flow<Int> = flow { kotlinx.coroutines.withContext(Dispatchers.Default) { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } } }
  177. 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 ...
  178. fun main() = runBlocking<Unit> { // Collect the flow simple().collect

    { value -> println(value) } } fun simple(): Flow<Int> = flow { println("Flow started") for (i in 1 .. 3) { delay(100) emit(i) } }.flowOn(Dispatchers.Default)
  179. Thank You! Hadi Tok Android GDE @hadi_tok