$30 off During Our Annual Pro Sale. View Details »

Android Async Programming

Hadi Tok
April 25, 2021
52

Android Async Programming

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

Hadi Tok

April 25, 2021
Tweet

Transcript

  1. Hadi Tok

    Android GDE

    @hadi_tok
    Android Threading

    View Slide

  2. Basics

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  7. public static void main(String[] args) {

    Thread thread = new Thread() {

    @Override

    public void run() {

    System.out.println("this is a thread");

    }

    };

    thread.start();

    }

    View Slide

  8. fun main() {

    val thread = Thread {

    print("this is a thread")

    }

    thread.start()

    }

    View Slide

  9. Threads
    They are relatively expensive in java.

    View Slide

  10. Threads
    They are relatively expensive in java.

    Shouldn’t need to create threads for every sequence of
    instructions we run.

    View Slide

  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 …

    View Slide

  12. Runnables


    View Slide

  13. Runnables
    Interface to execute code on a thread.

    View Slide

  14. fun main() {

    val thread = Thread(Runnable{

    print("this is a thread")

    })

    thread.start()

    }

    View Slide

  15. Runnables
    • A thread can run multiple runnables

    View Slide

  16. Runnables
    • A thread can run multiple runnables

    • The runnables can be provided from the same thread or
    could be provided from external threads.

    View Slide

  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.

    View Slide

  18. Main thread


    View Slide

  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)

    View Slide

  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.

    View Slide

  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.

    View Slide

  22. MessageQueue


    View Slide

  23. What is MessageQueue
    What is MessageQueue?

    View Slide

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

    View Slide

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

    View Slide

  26. Looper


    View Slide

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

    View Slide

  28. View Slide

  29. View Slide

  30. for (
    ;;
    ) {

    Message msg = queue.next();
    //
    might block




    msg.target.dispatchMessage(msg);





    msg.recycleUnchecked();


    }

    View Slide

  31. public void dispatchMessage(@NonNull Message msg) {

    if (msg.callback
    !=
    null) {

    handleCallback(msg);

    } else {

    if (mCallback
    !=
    null) {

    if (mCallback.handleMessage(msg)) {

    return;

    }

    }

    handleMessage(msg);

    }

    }

    View Slide

  32. private static void handleCallback(Message message) {

    message.callback.run();

    }

    View Slide

  33. View Slide

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

    View Slide

  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.

    View Slide

  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. 🤔

    View Slide

  37. Handler


    View Slide

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

    View Slide

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


    View Slide

  40. RunnableX
    Runnable3 MessageA
    MessageQueue

    View Slide

  41. RunnableX
    Runnable3 MessageA
    MessageQueue
    Looper

    View Slide

  42. RunnableX
    Runnable3 MessageA
    MessageQueue
    Handler
    MessageN
    Looper

    View Slide

  43. RunnableX
    Runnable3 MessageA
    MessageQueue
    Handler
    MessageN
    Looper

    View Slide

  44. RunnableX
    Runnable3 MessageA
    MessageQueue
    Looper
    Handler
    MessageN

    View Slide

  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


    View Slide

  46. post()

    View Slide

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

    View Slide

  48. postAtFrontOfQueue()

    View Slide

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

    View Slide

  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.

    View Slide

  51. postDelayed()

    View Slide

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

    View Slide

  53. postAtTime()

    View Slide

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

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    View Slide

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

    }

    }

    🤔

    View Slide

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

    }

    }

    }

    View Slide

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

    }

    }

    }

    View Slide

  60. AsyncTask


    View Slide

  61. AsyncTask


    View Slide

  62. Executors


    View Slide

  63. Executor
    An object that executes submitted Runnable tasks.

    View Slide

  64. fun main() {

    val thread = Thread(Runnable{

    print("this is a thread")

    })

    thread.start()

    }

    View Slide

  65. fun main() {

    executor.execute(Runnable{

    print("this is a thread")

    })

    }

    View Slide

  66. fun main() {

    executor.execute {

    print("this is a thread")

    }

    }

    View Slide

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

    View Slide

  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.


    View Slide

  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.


    View Slide

  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.


    View Slide

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

    View Slide

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

    View Slide

  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,

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    }

    View Slide

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

    }

    View Slide

  80. 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))

    View Slide

  81. 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))

    }

    View Slide

  82. Thread Safety


    View Slide

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

    }

    }

    }

    View Slide

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

    }

    }

    }

    View Slide

  85. 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))

    }

    }

    View Slide

  86. 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))

    }

    }

    View Slide

  87. 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)

    }

    }

    View Slide

  88. 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)

    }

    }

    View Slide

  89. 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)

    }

    }

    View Slide

  90. 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”)

    }

    View Slide

  91. Thank You!
    Hadi Tok

    Android GDE

    @hadi_tok

    View Slide

  92. Hadi Tok

    Android GDE

    @hadi_tok
    Android Threading II

    View Slide

  93. Coroutines


    View Slide

  94. Long running tasks
    • Network requests

    • Database operations

    • Image processing

    • ...

    View Slide

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

    View Slide

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

    • Challenge 2: Get informed when the task is done

    View Slide

  97. fun login(username: String, pass: String) {

    Thread {

    requestLogin(username, pass) { result
    ->


    showResult(result)

    }

    }.run()

    }

    View Slide

  98. fun login(username: String, pass: String) {

    Thread {

    requestLogin(username, pass) { result
    ->


    Handler(Looper.getMainLooper()).post {

    showResult(result)

    }

    }

    }.run()

    }

    View Slide

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

    }

    }

    }

    View Slide

  100. fun login(username: String, pass: String) {

    requestLogin(username, pass) { result
    ->
    showResult(result) }

    }

    View Slide

  101. onDraw
    onDraw
    onDraw
    requestLogin
    UI Thread
    fun login(name, pass) {


    requestLogin(name, pass) { result -> show(result) }


    }


    View Slide

  102. onDraw
    onDraw
    onDraw
    requestLogin
    UI Thread
    fun login(name, pass) {


    requestLogin(name, pass) { result -> show(result) }


    }


    View Slide

  103. onDraw
    onDraw
    onDraw
    requestLogin
    UI Thread
    show
    onDraw
    fun login(name, pass) {


    requestLogin(name, pass) { result -> show(result) }


    }


    View Slide

  104. onDraw
    onDraw
    onDraw
    requestLogin
    UI Thread
    show
    onDraw
    fun login(name, pass) {


    requestLogin(name, pass) { result -> show(result) }


    }


    onDraw
    onDraw
    onDraw

    View Slide

  105. Coroutine


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

    View Slide

  106. Coroutine
    Write asynchronous code sequentially

    View Slide

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

    val result = requestLogin(username, pass)

    showResult(result)

    }

    View Slide

  108. fun login(username: String, pass: String) =

    GlobalScope.launch(Dispatchers.IO) {

    val result = requestLogin(username, pass)

    withContext(Dispatchers.Main) {

    showResult(result)

    }

    }

    View Slide

  109. fun login(name, pass) {


    requestLogin(name, pass) { result -> show(result) }


    }


    requestLogin
    Thread
    show
    Suspension point

    View Slide

  110. suspend fun requestLogin(username: String, pass: String): Result {

    delay(1000)
    //
    long running operation

    return Result.Success

    }

    View Slide

  111. suspend fun requestLogin(username: String, pass: String): Result {

    //
    delay(1000)
    //
    long running operation

    return Result.Success

    }

    View Slide

  112. suspend fun requestLogin(username: String, pass: String)

    : Result = withContext(Dispatchers.IO) {

    delay(1000)
    //
    long running operation

    return@withContext Result.Success

    }

    View Slide

  113. fun login(username: String, pass: String) =

    GlobalScope.launch(Dispatchers.Main) {

    val result = requestLogin(username, pass)

    showResult(result)

    }

    View Slide

  114. fun login(username: String, pass: String) =

    GlobalScope.launch(Dispatchers.Main) {

    val token = auth(username, pass)

    val result = requestLogin(token)

    showResult(result)

    }

    View Slide

  115. Dispatchers.Main


    ● Main thread


    ● Use it for: UI operations


    View Slide

  116. Dispatchers.Default


    ● Common pool of shared background threads


    ● Use it for: computing-intensive coroutines


    Dispatchers.Main


    ● Main thread


    ● Use it for: UI operations


    View Slide

  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


    View Slide

  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


    View Slide

  119. fun login(username: String, pass: String) =

    GlobalScope.launch(Dispatchers.Main) {

    val token = auth(username, pass)

    val result = requestLogin(token)

    showResult(result)

    }

    View Slide

  120. Error Handling
    Try Catch just works!

    View Slide

  121. try {

    val token = auth(username, pass)

    val result = requestLogin(token)

    showResult(result)

    } catch (e: Exception) {

    }

    View Slide

  122. Launching and cancelling coroutines
    Login button
    pressed
    Launch coroutine


    requestLogin
    Show login
    status

    View Slide

  123. Launching and cancelling coroutines
    Login button
    pressed
    Launch coroutine


    requestLogin
    Show login
    status
    Activity


    destroyed

    View Slide

  124. Launching and cancelling coroutines
    Login button
    pressed
    Launch coroutine


    requestLogin
    Show login
    status
    ViewModel

    View Slide

  125. val job = Job()

    val uiScope = CoroutineScope(Dispatchers.Main + job)

    View Slide

  126. fun login(username: String, pass: String) =

    GlobalScope.launch(Dispatchers.Main) {

    val token = auth(username, pass)

    val result = requestLogin(token)

    showResult(result)

    }

    View Slide

  127. fun login(username: String, pass: String) =

    uiScope.launch {

    val token = auth(username, pass)

    val result = requestLogin(token)

    showResult(result)

    }

    View Slide

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

    }

    View Slide

  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

    View Slide

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

    }

    View Slide

  131. fun login(username: String, pass: String) =

    viewModelScope.launch {

    val token = auth(username, pass)

    val result = requestLogin(token)

    showResult(result)

    }

    View Slide

  132. How to make
    coroutines cancellable?

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

  138. Composing
    Suspending functions

    View Slide

  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

    }

    View Slide

  140. val time = measureTimeMillis {

    val one = doSomethingUsefulOne()

    val two = doSomethingUsefulTwo()

    println("The answer is ${one + two}")

    }

    println("Completed in $time ms”)

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  148. Observable Pattern


    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  154. Flow


    View Slide

  155. 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

    View Slide

  156. 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

    }

    }

    View Slide

  157. fun main() = runBlocking {

    simple().collect { value
    ->
    println(value) }

    }

    View Slide

  158. 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

    }

    }

    View Slide

  159. I'm not blocked 1

    1

    I'm not blocked 2

    2

    I'm not blocked 3

    3

    View Slide

  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:

    View Slide

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

    }

    View Slide

  162. Calling simple function
    ...


    Calling collect
    ...


    Flow started

    1

    2

    3

    Calling collect again
    ...


    Flow started

    1

    2

    3

    View Slide

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

    }

    View Slide

  164. Calling simple function
    ...


    Calling collect
    ...


    Flow started

    1

    2

    3

    Calling collect again
    ...


    Flow started

    1

    2

    3

    View Slide

  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.

    View Slide

  166. Operators


    View Slide

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

    }

    View Slide

  168. 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

    View Slide

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

    }

    View Slide

  170. 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

    View Slide

  171. fun main() = runBlocking {

    (1
    ..
    3).asFlow()
    //
    a flow of requests

    .take(2)

    .collect { value
    ->
    println(value) }

    }

    View Slide

  172. fun main() = runBlocking {

    (1
    ..
    3).asFlow()
    //
    a flow of requests

    .take(2)

    .collect { value
    ->
    println(value) }

    }

    1

    2

    View Slide

  173. Flow context

    View Slide

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

    View Slide

  175. 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)

    }



    }

    View Slide

  176. 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)

    }

    }

    }

    View Slide

  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
    ...

    View Slide

  178. 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)

    View Slide

  179. Thank You!
    Hadi Tok

    Android GDE

    @hadi_tok

    View Slide