@@ -84,7 +84,7 @@ different UI application libraries:
84
84
* [ kotlinx-coroutines-swing] ( kotlinx-coroutines-swing ) -- ` Swing ` context for Swing UI applications.
85
85
86
86
This guide covers all UI libraries simultaneously, because each of these modules consists of just one
87
- object definition that is couple of pages long. You can use any of them as an example to write the corresponding
87
+ object definition that is a couple of pages long. You can use any of them as an example to write the corresponding
88
88
context object for your favourite UI library, even if it is not included out of the box here.
89
89
90
90
## Table of contents
@@ -111,9 +111,9 @@ context object for your favourite UI library, even if it is not included out of
111
111
112
112
## Setup
113
113
114
- The runnable examples in this guide are be presented for JavaFx. The advantage is that all the examples can
114
+ The runnable examples in this guide are presented for JavaFx. The advantage is that all the examples can
115
115
be directly started on any OS without the need for emulators or anything like that and they are fully self-contained
116
- (all code is in one file).
116
+ (each example is in one file).
117
117
There are separate notes on what changes need to be made (if any) to reproduce them on Android.
118
118
119
119
### JavaFx
@@ -145,17 +145,17 @@ experiment with them by making changes.
145
145
Follow the guide on [ Getting Started With Android and Kotlin] ( https://kotlinlang.org/docs/tutorials/kotlin-android.html )
146
146
to create Kotlin project in Android Studio. You are also encouraged to add
147
147
[ Kotlin Android Extensions] ( https://kotlinlang.org/docs/tutorials/android-plugin.html )
148
- to you application.
148
+ to your application.
149
149
150
150
In Android Studio 2.3 you'll get an application that looks similarly to the one shown below:
151
151
152
152
![ UI example for Android] ( ui-example-android.png )
153
153
154
- Go the ` context_main.xml ` of your application and assign an ID of "hello" to the text view with "Hello World!" string,
154
+ Go to the ` context_main.xml ` of your application and assign an ID of "hello" to the text view with "Hello World!" string,
155
155
so that it is available in your application as ` hello ` with Kotlin Android extensions. The pinkish floating
156
156
action button is already named ` fab ` in the project template that gets created.
157
157
158
- In the ` MainActivity.kt ` of you application remove the block ` fab.setOnClickListener { ... } ` and instead
158
+ In the ` MainActivity.kt ` of your application remove the block ` fab.setOnClickListener { ... } ` and instead
159
159
add ` setup(hello, fab) ` invocation as the last line of ` onCreate ` function.
160
160
Create a placeholder ` setup ` function at the end of the file.
161
161
That is where various code is placed in the rest of this guide:
@@ -203,7 +203,7 @@ import kotlinx.coroutines.experimental.javafx.JavaFx as UI
203
203
204
204
<!-- - CLEAR -->
205
205
206
- Coroutines confined to UI thread can freely update anything in UI and suspend without blocking the UI thread.
206
+ Coroutines confined to the UI thread can freely update anything in UI and suspend without blocking the UI thread.
207
207
For example, we can perform animations by coding them in imperative style. The following code updates the
208
208
text with a 10 to 1 countdown twice a second, using [ launch] coroutine builder:
209
209
@@ -231,7 +231,7 @@ while `delay` waits, because it does not block the UI thread -- it just suspends
231
231
### Cancel UI coroutine
232
232
233
233
We can keep a reference to the [ Job] object that ` launch ` function returns and use it to cancel
234
- coroutine when want to stop it. Let us cancel the coroutine when pinkish circle is clicked:
234
+ coroutine when we want to stop it. Let us cancel the coroutine when pinkish circle is clicked:
235
235
236
236
``` kotlin
237
237
fun setup (hello : Text , fab : Circle ) {
@@ -286,13 +286,13 @@ fun setup(hello: Text, fab: Circle) {
286
286
<!-- - INCLUDE .*/example-ui-actor-([0-9]+).kt -->
287
287
288
288
Our first implementation for ` onClick ` just launches a new coroutine on each mouse event and
289
- passes the corresponding mouse event into the block (just in case we need it):
289
+ passes the corresponding mouse event into the supplied action (just in case we need it):
290
290
291
291
``` kotlin
292
- fun Node.onClick (block : suspend (MouseEvent ) -> Unit ) {
292
+ fun Node.onClick (action : suspend (MouseEvent ) -> Unit ) {
293
293
onMouseClicked = EventHandler { event ->
294
294
launch(UI ) {
295
- block (event)
295
+ action (event)
296
296
}
297
297
}
298
298
}
@@ -308,10 +308,10 @@ update the text. Try it. It does not look very good. We'll fix it later.
308
308
on Android, so it is omitted.
309
309
310
310
``` kotlin
311
- fun View.onClick (block : suspend () -> Unit ) {
311
+ fun View.onClick (action : suspend () -> Unit ) {
312
312
setOnClickListener {
313
313
launch(UI ) {
314
- block ()
314
+ action ()
315
315
}
316
316
}
317
317
}
@@ -323,15 +323,15 @@ fun View.onClick(block: suspend () -> Unit) {
323
323
324
324
We can cancel an active job before starting a new one to ensure that at most one coroutine is animating
325
325
the countdown. However, it is generally not the best idea. The [ cancel] [ Job.cancel ] function serves only as a signal
326
- to abort coroutine. Cancellation is cooperative and coroutine may, at the moment, be doing something non-cancellable
326
+ to abort a coroutine. Cancellation is cooperative and a coroutine may, at the moment, be doing something non-cancellable
327
327
or otherwise ignore a cancellation signal. A better solution is to use an [ actor] for tasks that should
328
328
not be performed concurrently. Let us change ` onClick ` extension implementation:
329
329
330
330
``` kotlin
331
- fun Node.onClick (block : suspend (MouseEvent ) -> Unit ) {
331
+ fun Node.onClick (action : suspend (MouseEvent ) -> Unit ) {
332
332
// launch one actor to handle all events on this node
333
333
val eventActor = actor<MouseEvent >(UI ) {
334
- for (event in channel) block (event) // pass event to block
334
+ for (event in channel) action (event) // pass event to action
335
335
}
336
336
// install a listener to offer events to this actor
337
337
onMouseClicked = EventHandler { event ->
@@ -344,7 +344,7 @@ fun Node.onClick(block: suspend (MouseEvent) -> Unit) {
344
344
345
345
The key idea that underlies an integration of an actor coroutine and a regular event handler is that
346
346
there is an [ offer] [ SendChannel.offer ] function on [ SendChannel] that does not wait. It sends an element to the actor immediately,
347
- if it is possible, or discards an element otherwise. An ` offser ` actually returns a ` Boolean ` result which we ignore here.
347
+ if it is possible, or discards an element otherwise. An ` offer ` actually returns a ` Boolean ` result which we ignore here.
348
348
349
349
Try clicking repeatedly on a circle in this version of the code. The clicks are just ignored while the countdown
350
350
animation is running. This happens because the actor is busy with an animation and does not receive from its channel.
@@ -355,10 +355,10 @@ the `receive` is active.
355
355
The corresponding extension for ` View ` class looks like this:
356
356
357
357
``` kotlin
358
- fun View.onClick (block : suspend () -> Unit ) {
358
+ fun View.onClick (action : suspend () -> Unit ) {
359
359
// launch one actor
360
360
val eventActor = actor<Unit >(UI ) {
361
- for (event in channel) block ()
361
+ for (event in channel) action ()
362
362
}
363
363
// install a listener to activate this actor
364
364
setOnClickListener {
@@ -377,14 +377,14 @@ processing the previous one. The [actor] coroutine builder accepts an optional
377
377
controls the implementation of the channel that this actor is using for its mailbox. The description of all
378
378
the available choices is given in documentation of the [ Channel()] [ Channel.invoke ] factory function.
379
379
380
- Let us change to the code to use [ ConflatedChannel] by passing [ Channel.CONFLATED] capacity value. The
380
+ Let us change the code to use [ ConflatedChannel] by passing [ Channel.CONFLATED] capacity value. The
381
381
change is only to the line that creates an actor:
382
382
383
383
``` kotlin
384
- fun Node.onClick (block : suspend (MouseEvent ) -> Unit ) {
384
+ fun Node.onClick (action : suspend (MouseEvent ) -> Unit ) {
385
385
// launch one actor to handle all events on this node
386
386
val eventActor = actor<MouseEvent >(UI , capacity = Channel .CONFLATED ) { // <--- Changed here
387
- for (event in channel) block (event) // pass event to block
387
+ for (event in channel) action (event) // pass event to action
388
388
}
389
389
// install a listener to offer events to this actor
390
390
onMouseClicked = EventHandler { event ->
@@ -410,21 +410,21 @@ events. In this case, the animation runs as many times and the circle is clicked
410
410
411
411
## Blocking operations
412
412
413
- This section explains patterns on using UI coroutines with thread-blocking operations.
413
+ This section explains how to use UI coroutines with thread-blocking operations.
414
414
415
415
### The problem of UI freezes
416
416
417
- It would have been great if all APIs out there were written as suspending functions that never block an
417
+ It would have been great if all APIs out there were written as suspending functions that never blocks an
418
418
execution thread. However, it is quite often not the case. Sometimes you need to do a CPU-consuming computation
419
419
or just need to invoke some 3rd party APIs for network access, for example, that blocks the invoker thread.
420
- You can cannot do that from the UI thread nor from them UI-confined coroutine directly, because that would
420
+ You cannot do that from the UI thread nor from the UI-confined coroutine directly, because that would
421
421
block the UI thread and cause the freeze up of the UI.
422
422
423
423
<!-- - INCLUDE .*/example-ui-blocking-([0-9]+).kt
424
424
425
- fun Node.onClick(block : suspend (MouseEvent) -> Unit) {
425
+ fun Node.onClick(action : suspend (MouseEvent) -> Unit) {
426
426
val eventActor = actor<MouseEvent>(UI, capacity = Channel.CONFLATED) {
427
- for (event in channel) block (event) // pass event to block
427
+ for (event in channel) action (event) // pass event to action
428
428
}
429
429
onMouseClicked = EventHandler { event ->
430
430
eventActor.offer(event)
@@ -457,7 +457,7 @@ fun setup(hello: Text, fab: Circle) {
457
457
delay(100 ) // update the text every 100ms
458
458
}
459
459
}
460
- // compute next fibonacci number of each click
460
+ // compute the next fibonacci number of each click
461
461
var x = 1
462
462
fab.onClick {
463
463
result = " fib($x ) = ${fib(x)} "
@@ -479,7 +479,7 @@ The fix for the blocking operations on the UI thread is quite straightforward wi
479
479
convert our "blocking" ` fib ` function to a non-blocking suspending function that runs the computation in
480
480
the background thread by using [ run] function to change its execution context to [ CommonPool] of background
481
481
threads. Notice, that ` fib ` function is now marked with ` suspend ` modifier. It does not block the coroutine that
482
- it is invoked from anymore, but suspends its execution when the computation in background thread is working:
482
+ it is invoked from anymore, but suspends its execution when the computation in the background thread is working:
483
483
484
484
<!-- - INCLUDE .*/example-ui-blocking-0[23].kt
485
485
@@ -514,8 +514,8 @@ You can run this code and verify that UI is not frozen while large Fibonacci num
514
514
However, this code computes ` fib ` somewhat slower, because every recursive call to ` fib ` goes via ` run ` . This is
515
515
not a big problem in practice, because ` run ` is smart enough to check that the coroutine is already running
516
516
in the required context and avoids overhead of dispatching coroutine to a different thread again. It is an
517
- overhead nonetheless, which is visible on this primitive code that does nothing else of use , but only adds integers
518
- in between invocations to ` run ` . For some more substantial code, the overhead of an extra ` run ` invocation is
517
+ overhead nonetheless, which is visible on this primitive code that does nothing else, but only adds integers
518
+ in between invocations to ` run ` . For some more substantial code, the overhead of an extra ` run ` invocation is
519
519
not going to be significant.
520
520
521
521
Still, this particular ` fib ` implementation can be made to run as fast as before, but in the background thread, by renaming
@@ -532,11 +532,11 @@ fun fibBlocking(x: Int): Int =
532
532
533
533
> You can get full code [ here] ( kotlinx-coroutines-javafx/src/test/kotlin/guide/example-ui-blocking-03.kt ) .
534
534
535
- You can now enjoy full-speed naive Fibonacci computation without blocking UI thread. All we need is ` run(CommonPool) ` .
535
+ You can now enjoy full-speed naive Fibonacci computation without blocking the UI thread. All we need is ` run(CommonPool) ` .
536
536
537
- Note, that because the ` fib ` function is invoked from a single actor in our code, there is at most one concurrent
537
+ Note, that because the ` fib ` function is invoked from the single actor in our code, there is at most one concurrent
538
538
computation of it at any given time, so this code has a natural limit on the resource utilization.
539
- It can saturate at most on CPU core at any given time .
539
+ It can saturate at most one CPU core.
540
540
541
541
## Lifecycle
542
542
@@ -546,15 +546,15 @@ This section outlines an approach to life-cycle management with coroutines.
546
546
547
547
A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments
548
548
and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background
549
- computation, can retain reference to the corresponding UI elements for longer than it is needed, preventing garbage
549
+ computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage
550
550
collection of the whole trees of UI objects that were already destroyed and will not be displayed anymore.
551
551
552
552
The natural solution to this problem is to associate a [ Job] object with each UI object that has a lifecycle and create
553
553
all the coroutines in the context of this job.
554
554
555
555
For example, in Android application an ` Activity ` is initially _ created_ and is _ destroyed_ when it is no longer
556
556
needed and when its memory must be released. A natural solution is to attach an
557
- instance of ` Job ` to an instance of ` Activity ` . We can create a mini-framework for that,
557
+ instance of a ` Job ` to an instance of an ` Activity ` . We can create a mini-framework for that,
558
558
by defining the following ` JobHolder ` interface:
559
559
560
560
``` kotlin
@@ -568,7 +568,7 @@ its `onDestroy` function to cancel the corresponding job:
568
568
569
569
``` kotlin
570
570
class MainActivity : AppCompatActivity (), JobHolder {
571
- override val job: Job = Job () // an instance of Job for this activity
571
+ override val job: Job = Job () // the instance of a Job for this activity
572
572
573
573
override fun onDestroy () {
574
574
super .onDestroy()
@@ -580,7 +580,7 @@ class MainActivity : AppCompatActivity(), JobHolder {
580
580
```
581
581
582
582
We also need a convenient way to retrieve a job for any view in the application. This is straightforward, because
583
- an activity is a context of the views in it, so we can define the following ` View.contextJob ` extension property:
583
+ an activity is an Android ` Context ` of the views in it, so we can define the following ` View.contextJob ` extension property:
584
584
585
585
``` kotlin
586
586
val View .contextJob: Job
@@ -590,17 +590,17 @@ val View.contextJob: Job
590
590
Here we use [ NonCancellable] implementation of the ` Job ` as a null-object for the case where our ` contextJob `
591
591
extension property is invoked in a context that does not have an attached job.
592
592
593
- As convenience of having a ` contextJob ` available is that we can simply use it to start all the coroutines
593
+ A convenience of having a ` contextJob ` available is that we can simply use it to start all the coroutines
594
594
without having to worry about explicitly maintaining a list of the coroutines we had started.
595
595
All the life-cycle management will be taken care of by the mechanics of parent-child relations between jobs.
596
596
597
597
For example, ` View.onClick ` extension from the previous section can now be defined using ` contextJob ` :
598
598
599
599
``` kotlin
600
- fun View.onClick (block : suspend () -> Unit ) {
601
- // launch one actor as a paren of the context job
600
+ fun View.onClick (action : suspend () -> Unit ) {
601
+ // launch one actor as a parent of the context job
602
602
val eventActor = actor<Unit >(contextJob + UI , capacity = Channel .CONFLATED ) {
603
- for (event in channel) block ()
603
+ for (event in channel) action ()
604
604
}
605
605
// install a listener to activate this actor
606
606
setOnClickListener {
@@ -609,10 +609,10 @@ fun View.onClick(block: suspend () -> Unit) {
609
609
}
610
610
```
611
611
612
- Notice ` contextJob + UI ` expression that is used to start an actor in the above code. It defines a coroutine context
613
- for our new actor that includes the job and UI dispatcher. The coroutine that is started by this
612
+ Notice how ` contextJob + UI ` expression is used to start an actor in the above code. It defines a coroutine context
613
+ for our new actor that includes the job and the ` UI ` dispatcher. The coroutine that is started by this
614
614
` actor(contextJob + UI) ` expression is going to become a child of the job of the corresponding context. When the
615
- activity is destroyed and its job is cancelled, all its children coroutines are cancelled.
615
+ activity is destroyed and its job is cancelled all its children coroutines are cancelled, too .
616
616
617
617
Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of
618
618
the view and in its context can create further children coroutines. The whole tree of coroutines gets cancelled
0 commit comments