xref: /aosp_15_r20/external/kotlinx.coroutines/ui/coroutines-guide-ui.md (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)
1# Guide to UI programming with coroutines
2
3This guide assumes familiarity with basic coroutine concepts that are
4covered in [Guide to kotlinx.coroutines](../docs/topics/coroutines-guide.md) and gives specific
5examples on how to use coroutines in UI applications.
6
7All UI application libraries have one thing in common. They have the single main thread where all state of the UI
8is confined, and all updates to the UI has to happen in this particular thread. With respect to coroutines,
9it means that you need an appropriate _coroutine dispatcher context_ that confines the coroutine
10execution to this main UI thread.
11
12In particular, `kotlinx.coroutines` has three modules that provide coroutine context for
13different UI application libraries:
14
15* [kotlinx-coroutines-android](kotlinx-coroutines-android) -- `Dispatchers.Main` context for Android applications.
16* [kotlinx-coroutines-javafx](kotlinx-coroutines-javafx) -- `Dispatchers.JavaFx` context for JavaFX UI applications.
17* [kotlinx-coroutines-swing](kotlinx-coroutines-swing) -- `Dispatchers.Swing` context for Swing UI applications.
18
19Also, UI dispatcher is available via `Dispatchers.Main` from `kotlinx-coroutines-core` and corresponding
20implementation (Android, JavaFx or Swing) is discovered by [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) API.
21For example, if you are writing JavaFx application, you can use either `Dispatchers.Main` or `Dispachers.JavaFx` extension, it will be the same object.
22
23This guide covers all UI libraries simultaneously, because each of these modules consists of just one
24object definition that is a couple of pages long. You can use any of them as an example to write the corresponding
25context object for your favourite UI library, even if it is not included out of the box here.
26
27## Table of contents
28
29<!--- TOC -->
30
31* [Setup](#setup)
32  * [JavaFx](#javafx)
33  * [Android](#android)
34* [Basic UI coroutines](#basic-ui-coroutines)
35  * [Launch UI coroutine](#launch-ui-coroutine)
36  * [Cancel UI coroutine](#cancel-ui-coroutine)
37* [Using actors within UI context](#using-actors-within-ui-context)
38  * [Extensions for coroutines](#extensions-for-coroutines)
39  * [At most one concurrent job](#at-most-one-concurrent-job)
40  * [Event conflation](#event-conflation)
41* [Blocking operations](#blocking-operations)
42  * [The problem of UI freezes](#the-problem-of-ui-freezes)
43  * [Structured concurrency, lifecycle and coroutine parent-child hierarchy](#structured-concurrency-lifecycle-and-coroutine-parent-child-hierarchy)
44  * [Blocking operations](#blocking-operations)
45* [Advanced topics](#advanced-topics)
46  * [Starting coroutine in UI event handlers without dispatch](#starting-coroutine-in-ui-event-handlers-without-dispatch)
47
48<!--- END -->
49
50## Setup
51
52The runnable examples in this guide are presented for JavaFx. The advantage is that all the examples can
53be directly started on any OS without the need for emulators or anything like that and they are fully self-contained
54(each example is in one file).
55There are separate notes on what changes need to be made (if any) to reproduce them on Android.
56
57### JavaFx
58
59The basic example application for JavaFx consists of a window with a text label named `hello` that initially
60contains "Hello World!" string and a pinkish circle in the bottom-right corner named `fab` (floating action button).
61
62![UI example for JavaFx](ui-example-javafx.png)
63
64The `start` function of JavaFX application invokes `setup` function, passing it reference to `hello` and `fab`
65nodes. That is where various code is placed in the rest of this guide:
66
67```kotlin
68fun setup(hello: Text, fab: Circle) {
69    // placeholder
70}
71```
72
73> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt).
74
75You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your
76workstation and open the project in IDE. All the examples from this guide are in the test folder of
77[`ui/kotlinx-coroutines-javafx`](kotlinx-coroutines-javafx) module.
78This way you'll be able to run and see how each example works and to
79experiment with them by making changes.
80
81### Android
82
83Follow the guide on [Getting Started With Android and Kotlin](https://kotlinlang.org/docs/tutorials/kotlin-android.html)
84to create Kotlin project in Android Studio. You are also encouraged to add
85[Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html)
86to your application.
87
88In Android Studio 2.3 you'll get an application that looks similarly to the one shown below:
89
90![UI example for Android](ui-example-android.png)
91
92Go to the `context_main.xml` of your application and assign an ID of "hello" to the text view with "Hello World!" string,
93so that it is available in your application as `hello` with Kotlin Android extensions. The pinkish floating
94action button is already named `fab` in the project template that gets created.
95
96In the `MainActivity.kt` of your application remove the block `fab.setOnClickListener { ... }` and instead
97add `setup(hello, fab)` invocation as the last line of `onCreate` function.
98Create a placeholder `setup` function at the end of the file.
99That is where various code is placed in the rest of this guide:
100
101```kotlin
102fun setup(hello: TextView, fab: FloatingActionButton) {
103    // placeholder
104}
105```
106
107<!--- CLEAR -->
108
109Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { ... }` section of
110`app/build.gradle` file:
111
112```groovy
113implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
114```
115
116You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your
117workstation. The resulting template project for Android resides in
118[`ui/kotlinx-coroutines-android/example-app`](kotlinx-coroutines-android/example-app) directory.
119You can load it in Android Studio to follow this guide on Android.
120
121## Basic UI coroutines
122
123This section shows basic usage of coroutines in UI applications.
124
125### Launch UI coroutine
126
127The `kotlinx-coroutines-javafx` module contains
128[Dispatchers.JavaFx][kotlinx.coroutines.Dispatchers.JavaFx]
129dispatcher that dispatches coroutine execution to
130the JavaFx application thread. We import it as `Main` to make all the presented examples
131easily portable to Android:
132
133```kotlin
134import kotlinx.coroutines.javafx.JavaFx as Main
135```
136
137<!--- CLEAR -->
138
139Coroutines confined to the main UI thread can freely update anything in UI and suspend without blocking the main thread.
140For example, we can perform animations by coding them in imperative style. The following code updates the
141text with a 10 to 1 countdown twice a second, using [launch] coroutine builder:
142
143```kotlin
144fun setup(hello: Text, fab: Circle) {
145    GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
146        for (i in 10 downTo 1) { // countdown from 10 to 1
147            hello.text = "Countdown $i ..." // update text
148            delay(500) // wait half a second
149        }
150        hello.text = "Done!"
151    }
152}
153```
154
155> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt).
156
157So, what happens here? Because we are launching coroutine in the main UI context, we can freely update UI from
158inside this coroutine and invoke _suspending functions_ like [delay] at the same time. UI is not frozen
159while `delay` waits, because it does not block the UI thread -- it just suspends the coroutine.
160
161> The corresponding code for Android application is the same.
162  You just need to copy the body of `setup` function into the corresponding function of Android project.
163
164### Cancel UI coroutine
165
166We can keep a reference to the [Job] object that `launch` function returns and use it to cancel
167coroutine when we want to stop it. Let us cancel the coroutine when pinkish circle is clicked:
168
169```kotlin
170fun setup(hello: Text, fab: Circle) {
171    val job = GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
172        for (i in 10 downTo 1) { // countdown from 10 to 1
173            hello.text = "Countdown $i ..." // update text
174            delay(500) // wait half a second
175        }
176        hello.text = "Done!"
177    }
178    fab.onMouseClicked = EventHandler { job.cancel() } // cancel coroutine on click
179}
180```
181
182> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt).
183
184Now, if the circle is clicked while countdown is still running, the countdown stops.
185Note that [Job.cancel] is completely thread-safe and non-blocking. It just signals the coroutine to cancel
186its job, without waiting for it to actually terminate. It can be invoked from anywhere.
187Invoking it on a coroutine that was already cancelled or has completed does nothing.
188
189> The corresponding line for Android is shown below:
190
191```kotlin
192fab.setOnClickListener { job.cancel() }  // cancel coroutine on click
193```
194
195<!--- CLEAR -->
196
197## Using actors within UI context
198
199In this section we show how UI applications can use actors within their UI context make sure that
200there is no unbounded growth in the number of launched coroutines.
201
202### Extensions for coroutines
203
204Our goal is to write an extension _coroutine builder_ function named `onClick`,
205so that we can perform countdown animation every time when the circle is clicked with this simple code:
206
207```kotlin
208fun setup(hello: Text, fab: Circle) {
209    fab.onClick { // start coroutine when the circle is clicked
210        for (i in 10 downTo 1) { // countdown from 10 to 1
211            hello.text = "Countdown $i ..." // update text
212            delay(500) // wait half a second
213        }
214        hello.text = "Done!"
215    }
216}
217```
218
219<!--- INCLUDE .*/example-ui-actor-([0-9]+).kt -->
220
221Our first implementation for `onClick` just launches a new coroutine on each mouse event and
222passes the corresponding mouse event into the supplied action (just in case we need it):
223
224```kotlin
225fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
226    onMouseClicked = EventHandler { event ->
227        GlobalScope.launch(Dispatchers.Main) {
228            action(event)
229        }
230    }
231}
232```
233
234> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt).
235
236Note that each time the circle is clicked, it starts a new coroutine and they all compete to
237update the text. Try it. It does not look very good. We'll fix it later.
238
239> On Android, the corresponding extension can be written for `View` class, so that the code
240  in `setup` function that is shown above can be used without changes. There is no `MouseEvent`
241  used in OnClickListener on Android, so it is omitted.
242
243```kotlin
244fun View.onClick(action: suspend () -> Unit) {
245    setOnClickListener {
246        GlobalScope.launch(Dispatchers.Main) {
247            action()
248        }
249    }
250}
251```
252
253<!--- CLEAR -->
254
255### At most one concurrent job
256
257We can cancel an active job before starting a new one to ensure that at most one coroutine is animating
258the countdown. However, it is generally not the best idea. The [cancel][Job.cancel] function serves only as a signal
259to abort a coroutine. Cancellation is cooperative and a coroutine may, at the moment, be doing something non-cancellable
260or otherwise ignore a cancellation signal. A better solution is to use an [actor] for tasks that should
261not be performed concurrently. Let us change `onClick` extension implementation:
262
263```kotlin
264fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
265    // launch one actor to handle all events on this node
266    val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main) {
267        for (event in channel) action(event) // pass event to action
268    }
269    // install a listener to offer events to this actor
270    onMouseClicked = EventHandler { event ->
271        eventActor.trySend(event)
272    }
273}
274```
275
276> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt).
277
278The key idea that underlies an integration of an actor coroutine and a regular event handler is that
279there is an [trySend][SendChannel.trySend] function on [SendChannel] that does not wait. It sends an element to the actor immediately,
280if it is possible, or discards an element otherwise. A `trySend` actually returns a `ChanneResult` instance which we ignore here.
281
282Try clicking repeatedly on a circle in this version of the code. The clicks are just ignored while the countdown
283animation is running. This happens because the actor is busy with an animation and does not receive from its channel.
284By default, an actor's mailbox is backed by `RendezvousChannel`, whose `trySend` operation succeeds only when
285the `receive` is active.
286
287> On Android, there is `View` sent in OnClickListener, so we send the `View` to the actor as a signal.
288  The corresponding extension for `View` class looks like this:
289
290```kotlin
291fun View.onClick(action: suspend (View) -> Unit) {
292    // launch one actor
293    val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
294        for (event in channel) action(event)
295    }
296    // install a listener to activate this actor
297    setOnClickListener {
298        eventActor.trySend(it)
299    }
300}
301```
302
303<!--- CLEAR -->
304
305
306### Event conflation
307
308Sometimes it is more appropriate to process the most recent event, instead of just ignoring events while we were busy
309processing the previous one.  The [actor] coroutine builder accepts an optional `capacity` parameter that
310controls the implementation of the channel that this actor is using for its mailbox. The description of all
311the available choices is given in documentation of the [`Channel()`][Channel] factory function.
312
313Let us change the code to use a conflated channel by passing [Channel.CONFLATED][Channel.Factory.CONFLATED] capacity value. The
314change is only to the line that creates an actor:
315
316```kotlin
317fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
318    // launch one actor to handle all events on this node
319    val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { // <--- Changed here
320        for (event in channel) action(event) // pass event to action
321    }
322    // install a listener to send events to this actor
323    onMouseClicked = EventHandler { event ->
324        eventActor.trySend(event)
325    }
326}
327```
328
329> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt).
330  On Android you need to update `val eventActor = ...` line from the previous example.
331
332Now, if a circle is clicked while the animation is running, it restarts animation after the end of it. Just once.
333Repeated clicks while the animation is running are _conflated_ and only the most recent event gets to be
334processed.
335
336This is also a desired behaviour for UI applications that have to react to incoming high-frequency
337event streams by updating their UI based on the most recently received update. A coroutine that is using
338a conflated channel (`capacity = Channel.CONFLATED`, or a buffered channel with
339`onBufferOverflow = DROP_OLDEST` or `onBufferOverflow = DROP_LATEST`) avoids delays
340that are usually introduced by buffering of events.
341
342You can experiment with `capacity` parameter in the above line to see how it affects the behaviour of the code.
343Setting `capacity = Channel.UNLIMITED` creates a coroutine with an unbounded mailbox that buffers all
344events. In this case, the animation runs as many times as the circle is clicked.
345
346## Blocking operations
347
348This section explains how to use UI coroutines with thread-blocking operations.
349
350### The problem of UI freezes
351
352It would have been great if all APIs out there were written as suspending functions that never blocks an
353execution thread. However, it is quite often not the case. Sometimes you need to do a CPU-consuming computation
354or just need to invoke some 3rd party APIs for network access, for example, that blocks the invoker thread.
355You cannot do that from the main UI thread nor from the UI-confined coroutine directly, because that would
356block the main UI thread and cause the freeze up of the UI.
357
358<!--- INCLUDE .*/example-ui-blocking-([0-9]+).kt
359fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
360    val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) {
361        for (event in channel) action(event) // pass event to action
362    }
363    onMouseClicked = EventHandler { event ->
364        eventActor.trySend(event)
365    }
366}
367-->
368
369The following example illustrates the problem. We are going to use `onClick` extension with UI-confined
370event-conflating actor from the last section to process the last click in the main UI thread.
371For this example, we are going to
372perform naive computation of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number):
373
374```kotlin
375fun fib(x: Int): Int =
376    if (x <= 1) x else fib(x - 1) + fib(x - 2)
377```
378
379We'll be computing larger and larger Fibonacci number each time the circle is clicked.
380To make the UI freeze more obvious, there is also a fast counting animation that is always running
381and is constantly updating the text in the main UI dispatcher:
382
383```kotlin
384fun setup(hello: Text, fab: Circle) {
385    var result = "none" // the last result
386    // counting animation
387    GlobalScope.launch(Dispatchers.Main) {
388        var counter = 0
389        while (true) {
390            hello.text = "${++counter}: $result"
391            delay(100) // update the text every 100ms
392        }
393    }
394    // compute the next fibonacci number of each click
395    var x = 1
396    fab.onClick {
397        result = "fib($x) = ${fib(x)}"
398        x++
399    }
400}
401```
402
403> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt).
404  You can just copy the `fib` function and the body of the `setup` function to your Android project.
405
406Try clicking on the circle in this example. After around 30-40th click our naive computation is going to become
407quite slow and you would immediately see how the main UI thread freezes, because the animation stops running
408during UI freeze.
409
410### Structured concurrency, lifecycle and coroutine parent-child hierarchy
411
412A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments
413and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background
414computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage
415collection of the whole trees of UI objects that were already destroyed and will not be displayed anymore.
416
417The natural solution to this problem is to associate a [CoroutineScope] object with each UI object that has a
418lifecycle and create all the coroutines in the context of this scope.
419For the sake of simplicity, [MainScope()] factory can be used. It automatically provides `Dispatchers.Main` and
420a parent job for all the children coroutines.
421
422For example, in Android application an `Activity` is initially _created_ and is _destroyed_ when it is no longer
423needed and when its memory must be released. A natural solution is to attach an
424instance of a `CoroutineScope` to an instance of an `Activity`:
425
426<!--- CLEAR -->
427
428```kotlin
429class MainActivity : AppCompatActivity() {
430    private val scope = MainScope()
431
432    override fun onDestroy() {
433        super.onDestroy()
434        scope.cancel()
435    }
436
437    fun asyncShowData() = scope.launch { // Is invoked in UI context with Activity's scope as a parent
438        // actual implementation
439    }
440
441    suspend fun showIOData() {
442        val data = withContext(Dispatchers.IO) {
443            // compute data in background thread
444        }
445        withContext(Dispatchers.Main) {
446            // Show data in UI
447        }
448    }
449}
450```
451
452Every coroutine launched from within a `MainActivity` has its job as a parent and is immediately cancelled when
453activity is destroyed.
454
455> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
456See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
457
458Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of
459the activity can create further children coroutines. The whole tree of coroutines gets cancelled
460when the parent job is cancelled. An example of that is shown in the
461["Children of a coroutine"](../docs/topics/coroutine-context-and-dispatchers.md#children-of-a-coroutine) section of the guide to coroutines.
462
463<!--- CLEAR -->
464
465### Blocking operations
466
467The fix for the blocking operations on the main UI thread is quite straightforward with coroutines. We'll
468convert our "blocking" `fib` function to a non-blocking suspending function that runs the computation in
469the background thread by using [withContext] function to change its execution context to [Dispatchers.Default] that is
470backed by the background pool of threads.
471Notice, that `fib` function is now marked with `suspend` modifier. It does not block the coroutine that
472it is invoked from anymore, but suspends its execution when the computation in the background thread is working:
473
474<!--- INCLUDE .*/example-ui-blocking-0[23].kt
475
476fun setup(hello: Text, fab: Circle) {
477    var result = "none" // the last result
478    // counting animation
479    GlobalScope.launch(Dispatchers.Main) {
480        var counter = 0
481        while (true) {
482            hello.text = "${++counter}: $result"
483            delay(100) // update the text every 100ms
484        }
485    }
486    // compute next fibonacci number of each click
487    var x = 1
488    fab.onClick {
489        result = "fib($x) = ${fib(x)}"
490        x++
491    }
492}
493-->
494
495```kotlin
496suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) {
497    if (x <= 1) x else fib(x - 1) + fib(x - 2)
498}
499```
500
501> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt).
502
503You can run this code and verify that UI is not frozen while large Fibonacci numbers are being computed.
504However, this code computes `fib` somewhat slower, because every recursive call to `fib` goes via `withContext`. This is
505not a big problem in practice, because `withContext` is smart enough to check that the coroutine is already running
506in the required context and avoids overhead of dispatching coroutine to a different thread again. It is an
507overhead nonetheless, which is visible on this primitive code that does nothing else, but only adds integers
508in between invocations to `withContext`. For some more substantial code, the overhead of an extra `withContext` invocation is
509not going to be significant.
510
511Still, this particular `fib` implementation can be made to run as fast as before, but in the background thread, by renaming
512the original `fib` function to `fibBlocking` and defining `fib` with `withContext` wrapper on top of `fibBlocking`:
513
514```kotlin
515suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) {
516    fibBlocking(x)
517}
518
519fun fibBlocking(x: Int): Int =
520    if (x <= 1) x else fibBlocking(x - 1) + fibBlocking(x - 2)
521```
522
523> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt).
524
525You can now enjoy full-speed naive Fibonacci computation without blocking the main UI thread.
526All we need is `withContext(Dispatchers.Default)`.
527
528Note that because the `fib` function is invoked from the single actor in our code, there is at most one concurrent
529computation of it at any given time, so this code has a natural limit on the resource utilization.
530It can saturate at most one CPU core.
531
532## Advanced topics
533
534This section covers various advanced topics.
535
536### Starting coroutine in UI event handlers without dispatch
537
538Let us write the following code in `setup` to visualize the order of execution when coroutine is launched
539from the UI thread:
540
541<!--- CLEAR -->
542
543```kotlin
544fun setup(hello: Text, fab: Circle) {
545    fab.onMouseClicked = EventHandler {
546        println("Before launch")
547        GlobalScope.launch(Dispatchers.Main) {
548            println("Inside coroutine")
549            delay(100)
550            println("After delay")
551        }
552        println("After launch")
553    }
554}
555```
556
557> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt).
558
559When we start this code and click on a pinkish circle, the following messages are printed to the console:
560
561```text
562Before launch
563After launch
564Inside coroutine
565After delay
566```
567
568As you can see, execution immediately continues after [launch], while the coroutine gets posted onto the main UI thread
569for execution later. All UI dispatchers in `kotlinx.coroutines` are implemented this way. Why so?
570
571Basically, the choice here is between "JS-style" asynchronous approach (async actions
572are always postponed to be executed later in the event dispatch thread) and "C#-style" approach
573(async actions are executed in the invoker thread until the first suspension point).
574While, C# approach seems to be more efficient, it ends up with recommendations like
575"use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent
576and does not require programmers to think about whether they need to yield or not.
577
578However, in this particular case when coroutine is started from an event handler and there is no other code around it,
579this extra dispatch does indeed add an extra overhead without bringing any additional value.
580In this case an optional [CoroutineStart] parameter to [launch], [async] and [actor] coroutine builders
581can be used for performance optimization.
582Setting it to the value of [CoroutineStart.UNDISPATCHED] has the effect of starting to execute
583coroutine immediately until its first suspension point as the following example shows:
584
585```kotlin
586fun setup(hello: Text, fab: Circle) {
587    fab.onMouseClicked = EventHandler {
588        println("Before launch")
589        GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { // <--- Notice this change
590            println("Inside coroutine")
591            delay(100)                            // <--- And this is where coroutine suspends
592            println("After delay")
593        }
594        println("After launch")
595    }
596}
597```
598
599> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt).
600
601It prints the following messages on click, confirming that code in the coroutine starts to execute immediately:
602
603```text
604Before launch
605Inside coroutine
606After launch
607After delay
608```
609
610<!--- MODULE kotlinx-coroutines-core -->
611<!--- INDEX kotlinx.coroutines -->
612
613[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
614[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
615[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
616[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
617[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
618[MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
619[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
620[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
621[CoroutineStart]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
622[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
623[CoroutineStart.UNDISPATCHED]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d/index.html
624
625<!--- INDEX kotlinx.coroutines.channels -->
626
627[actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
628[SendChannel.trySend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
629[SendChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
630[Channel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
631[Channel.Factory.CONFLATED]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-factory/-c-o-n-f-l-a-t-e-d.html
632
633<!--- MODULE kotlinx-coroutines-javafx -->
634<!--- INDEX kotlinx.coroutines.javafx -->
635
636[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/-java-fx.html
637
638<!--- MODULE kotlinx-coroutines-android -->
639<!--- INDEX kotlinx.coroutines.android -->
640<!--- END -->
641