Lean async code with Kotlin´s coroutines
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Alternative Title |
| |
Title of Series | ||
Number of Parts | 90 | |
Author | ||
License | CC Attribution 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor. | |
Identifiers | 10.5446/47634 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
2
10
13
14
26
35
37
45
48
54
55
56
63
64
68
73
74
77
78
80
82
85
89
00:00
CodeCoroutineWritingAreaCodeCausalityWeb pageMobile WebCoroutineBitRight angleCovering spaceDemoscenePlastikkarteArithmetic meanComputer animation
00:34
CodeCoroutineRepository (publishing)WritingSynchronizationExecution unitSoftwareThread (computing)Repository (publishing)Client (computing)SequenceCodeMultiplication signProcess (computing)MathematicsComputer architectureReal numberSynchronizationMobile appDifferent (Kate Ryan album)State observerSoftware repositoryData conversionTurtle graphicsCivil engineeringService (economics)Computer animation
02:32
CoroutineCodeWritingFunction (mathematics)Computer programmingFinite-state machineCompilerCoroutineComplex (psychology)Functional (mathematics)CodeOpen sourceState of matterSubsetImplementationFormal languageCASE <Informatik>Library (computing)Extension (kinesiology)Asynchronous Transfer ModePoint (geometry)Group actionBitCausalitySequenceSuspension (chemistry)AreaNumbering schemeMoment (mathematics)Service (economics)Computer animation
04:42
CoroutineWritingCodeDisintegrationLink (knot theory)Order (biology)CoroutineAreaService (economics)RoutingLine (geometry)KnotINTEGRALComputer animation
05:08
CoroutineWritingCodeDisintegrationLink (knot theory)Order (biology)Cartesian coordinate systemCoordinate systemINTEGRALCombinational logicLine (geometry)TrailComputer animation
05:31
CoroutineCodeWritingDisintegrationLink (knot theory)Order (biology)String (computer science)AdditionDot productComputer animation
05:54
FreewareCoroutineCodeWritingDisintegrationLink (knot theory)Dependent and independent variablesAddress spaceLattice (order)Computer networkParallel portAbstractionBuildingExecution unitVideo gameReverse engineeringProcess (computing)Operator (mathematics)String (computer science)Directed graphTask (computing)Parallel portFunctional (mathematics)Internet service providerMilitary baseWeightCycle (graph theory)CASE <Informatik>BitResultantGodStreaming mediaCivil engineeringDependent and independent variablesState observerData structurePlastikkarteMultiplication signData conversionExtension (kinesiology)1 (number)Block (periodic table)CodeCore dumpSocial classCoroutineElectronic mailing listLattice (order)SynchronizationLambda calculusSelectivity (electronic)CompilerSystem callAddress spaceComputer animation
12:48
Dependent and independent variablesCoroutineWritingCodeExecution unitPay televisionAbstractionFunction (mathematics)Order (biology)Data storage deviceBitDependent and independent variablesStreaming mediaOperator (mathematics)Channel capacityMathematicsRight angleCalculationFunctional (mathematics)SoftwareCASE <Informatik>Uniform resource locatorMultiplication signPay televisionEscape characterMereologyType theoryLibrary (computing)WaveBroadcasting (networking)Data streamGreatest elementMessage passingCodeSlide ruleQueue (abstract data type)TrailBoilerplate (text)AbstractionOpen setInformationComputer animation
16:58
CoroutineCodeWritingLoginLibrary (computing)CoroutineCondition numberProcess (computing)Slide ruleCurvaturePoint (geometry)LoginLevel (video gaming)Right angleCodeStructural loadMachine codeMultiplication signCASE <Informatik>SequenceComputer animation
18:42
CoroutineSequenceRegulärer Ausdruck <Textverarbeitung>WritingCodeComputer architectureAbstractionSoftware frameworkDisintegrationStreaming mediaJava appletComputing platformMultiplicationJava appletCuboidCoroutineCodeCross-platformStreaming mediaComputing platformINTEGRALMachine codeLibrary (computing)SequenceImplementationSoftware frameworkWritingDirected graphAdditionComputer animation
19:55
CoroutineWritingCodeGamma functionComputer animation
Transcript: English(auto-generated)
00:00
So, hey, all. My name is Ronen. I'm working in a company which is called Get. It's like an international player in the mobility area. And today, we're going to talk about asynchronous code with Kotlin coroutines. So first of all, we're going to discuss and to put all of us on the same page about what I mean
00:21
with asynchronous code. After that, we're going to discuss a bit what is coroutines. We're going to see coroutines in practice. In the end, we're going to sum it with why we need to use coroutines. All right. So async code. Basically, async code, it's meaning, not surprising,
00:41
you're going to run the code not in sequence way. So basically, most of the time, you're going to have some callback. And you're going to do some job. And after that, you're going to call to this callback. And I don't believe that there is a real app today that doesn't use any async code.
01:00
So I want to emphasize the difference between observation and synchronization. So let's say that we have this architecture that, for example, taking a repo from GitHub and display it on the UI. So the UI will call to Interactor. We give it some callback, because we can do,
01:22
for example, network on the main thread. So we need to do it in another thread. The Interactor first will enqueue the request with the network client. Let's say that we're using retrofit, so it's going to enqueue it. And after that, you're going to go to the repository
01:40
and put the data there. So this was like a synchronization. Oh, it's going to call first to Interactor to give it back the data that it took from the network. And then the Interactor going to put it in the repository. So it was like a synchronization between the work with, first of all, I need to get actually the data from the network.
02:00
And only after that, I can put it in the repository. So after that, the Interactor will go to the UI. And the UI finally can start to observe the data from the repository. So we have two models. We have the observation, which it's going to be pushed to the UI every time that a change is
02:21
going to be in the repository. And in the beginning of that, we have the synchronization. I need to do this first, and only then I can do the other job that I need to do. All right, so what is coroutines? Coroutines is basically just a piece of code that can be suspended and resumed.
02:41
In Kotlin, each coroutine is going to be compiled by the compiler to a state machine. And each suspendable place, a suspendable function is going to be like a state in the state machine. It's very old concept.
03:00
Kotlin didn't invent it. It basically started sometimes in the 70s. There is implementation of that with C, C++ scheme, and even with some more modern languages like C Sharp and Golang.
03:22
What's really nice about the coroutines that the compiler, at least in the case of Kotlin, the compiler is going to hide from me all the complexity. And the code inside the coroutines can be sequenced. And it's really nice. Kotlin support coroutines starting from 1.1.
03:44
It's still in experimental mode, but it's not because we're just thinking whether or not to kill it. It's because they want to polish the APIs before we're going to declare it as release. So it's OK to use it. In Kotlin, there is like suspendable function
04:02
which mark with suspend function. And only coroutines can run the suspend functions. So like I told you before, there is suspend function. Each one of them actually is going to be translated to some state in the state machine. And by that, we can gain the nice readability
04:22
with coroutines. And most of the coroutines that's written by JetBrain, it's coming from this extension library. It's called KotlinX coroutines. And it's really open source. And everybody can contribute to that. They do accept external pull requests.
04:43
So let's see coroutines in practice. So like I told you, I'm working in a company which called Jet in the mobility area. And among other services that we have, we have like pre-routes, which was predefined, like fixed routes.
05:01
And there is like cars going along the line. And everybody can just pop in and pop out from the car. And we have also integration with application which called CityMapper, which CityMapper provide us the lat and the long of like the GPS coordination
05:22
of the journey that the user want to do. And we need to do a lot of things. We need to calculate what is the fastest walking path to the line and from the line and where we need to go, when we need to go off. And there is a lot of things that we want to do.
05:40
And we want it's going to be smooth. So for that, we need to gain some performance. In addition, we have like there is like the green dots there, but it's the walking path. And we're going to change to convert the GPS coordinate to the actual address, like a string. And a lot of things that we want to do.
06:01
And we want to do it as fast as we can. So for now, we're going to deep dive just for the conversion of the GPS location, the lat and the long, to string. So in this case, we have like a function which called handleDeepLink. It's getting the request. Everything that it needs reside inside the request.
06:23
And everything that's going to go back to whoever call it, it's going to be in the response. So first of all, we want to take the start lat long and to convert it like a string, and the same thing with the at lat long. And by that, we can just put in the UI the addresses
06:43
instead just to give a nowhere location. So this is how we're going to do it in synchronous way. We're going to do the first one, and after that, we're going to do the second one. But I can actually do it in parallel.
07:01
So in coroutines, I can do it like this in parallel. I'm going to first explain what is the launch coroutine. So launch, it's a function, it's coroutine function, which gets a lambda. Everything that's inside the lambda is going to run immediately. The launch returns a job, a structure that's called a job,
07:22
and I can do several things with it. And eventually, what is going to happen here in the code, it's going to run the first one. And the second one, it's going to run in parallel. Now, the second thing that I'm going to show you here is the run blocking. When I want to wait for something to happen before I'm going to continue with the execution,
07:43
I'm going to put it inside block of run blocking. The join that we're doing inside the run blocking, it's suspend operation. This is actually suspend function. And this is how the compiler know how to translate it.
08:02
And because of that, you can just put it like that. We don't need to give some callbacks for that. So it's really nice. It's working. But I want to make it a bit more readable. So I can just wrap the launch coroutines with my function, which I call it in parallel,
08:20
because for me, it's mean a lot. And it's going to get just a task. Instead of doing the run blocking inside the code, I'm going to introduce extension function for list of jobs, which will do actually the same thing. It's going to take everything inside the list and join. So it's going to wait until all the jobs inside this list
08:43
will end. And finally, I want also this task will be bounded to some lifecycle. And I can do it very easily by introducing something that I call it JobCanceller, which is also Lifecycle Observer. So when I just initialize this class, this JobCanceller,
09:07
I can register it to some lifecycle owner. So it will be aware to the lifecycle. On the struggle of this lifecycle, I will just go over each one of the jobs in the list
09:23
and going to cancel it. And by that, it's going to be bounded to lifecycle. All right, so with these abstractions, my code looks a bit nicer, at least for me. Now it's written. I'm doing two things in parallel.
09:40
And then I'm going to put all of them in the JobCanceller, so it automatically will be closed when the lifecycle will be destroyed. And finally, I'm going to wait for all. All right, so it's really nice. But now I have a different problem. I want to make two tasks to run in parallel,
10:00
but just take the first one of them. For example, let's say that we have two providers that know how to reverse gear code from a lat-long to some string. One of them is Google, and the other one is my best friend, startup. And I want to help him. So we're going to use both of them.
10:20
So now, instead of using the launch coroutine, we're going to use async. Async, unlike launch, it doesn't start the execution right away. It's going to wait a turn. We're going to do await or on await in this case.
10:41
And it's returning defer, which defers something that extends the job, but it can also bring me back value. So I'm going to have these two async blocks, and then I want to wait for the first one. So first of all, we're going to look on the select.
11:01
Select, it's another coroutines that know to select the first one of these jobs that will end first. So we have the first provider that we're going to await on it, on the second provider. And when I'm going to call to on await, it's literally going to start the work in their sync.
11:24
And with that, the first one that we'll finish, probably, of course, not Google. It's going to be my best friend, startup. We're going to get the result, and this is going to be, actually, the return value. And because we want to wait for the first one,
11:40
we wrap all of that with one blocking. So once again, it's working. It's really nice, but I want to make it more readable. So what I can do, like before, I'm going to provide my own abstractions. I'm going to wrap the async call with this function, which is called fetch address with provider.
12:01
All this select code is going to be wrapped in extension function, this time instead of jobs, of deferred jobs. And I'm going to wait for first. And now I can use my abstraction, and the code looks much more nicer. I have fetched the first one, fetched the second one,
12:24
put all of them in the job canceller because I still want to be bound to the lifecycle. And then I'm going just to wait for the first one. So it's much more nicer, much more elegant than before. So until now, we just saw examples
12:40
of synchronization between two jobs, but we want to wait for the first or do both of them in parallel. Let's see how can we observe things with coroutines. So in coroutines, there is this notion of channel. Channel is like a message queue which can connect between.
13:01
It's like network between two coroutines. And basically, channels have some capacity. So let's say that the channel have capacity of two. If I want to write something to the channel and it's fully occupied, so I'm going to be suspended until somebody will pull something
13:21
from this message queue. And if I want to receive something but it's empty, so once again, I'm going to be suspended and wait until somebody will write to the channel. In this case, we're going to use special channel. It's a broadcast channel, but it's not just a broadcast channel. Its capacity is confluent. What it basically means that we're
13:41
going to have a lot of subscribers or consumers which can start to consume this channel. Everybody of them, when you're going to first observe the change, you're going to get the latest value. And when I update a new value, all of them
14:01
get the same value. So we have this broadcast channel. And I just call it like a walking info stream. I'm going to take from the network the walking path. And then I want to put the initial walking path so the UI can start observing things.
14:21
And like I told you before, send its suspend operation. So I must wrap it with coroutines. And after that, I'm going to call some function which will do the calculation with the location and update the walking path every time that something has been changed.
14:41
And we're going to return it as response. In the UI side, what we need to do, we need to get this broadcast channel open subscription. This is the way that we open a new subscriber that's going to be notified about the changes. And then we're starting to consume it. Now, we're using here a while because I don't want
15:02
to get only once the value. I want it's going to be observed all the time. So we're going to use this while the channel is not closed. And I get something. If it's not null, I'm going to push it to the channel. And then everybody that's listening to this channel
15:20
can take it and do it whatever you want. But it's very complex. So I'm going to introduce additional abstraction of my own. So this time, I'm going to start with the data stream. So data stream is just something that gets subscription. Suscription is from type of receive channel.
15:42
And I'm going to introduce there two methods. One of them, I call it notifyOnChange, which basically hide all the boilerplate that I had to do in order to consume values from this channel. And I'm going to have for that close
16:00
because if I don't want to observe any change anymore, I can just close it. With that, I can just use another abstraction, which called datastore. And datastore hide for me the broadcast channel. And there is a setCurrentValue, which do everything that we need to do with the launch and shutdown.
16:23
And in the bottom, we have the getDataStream, which returned for me the data stream from the previous slide. And with that, my code is look. On the end, the deep link, it looks a bit nicer because I don't have to put the launch there. And it's nice.
16:41
But when I'm consuming these events, it looks much, much more elegant. Now all I need to do is just to take from the response the walk-in path datastore, get the data stream, and be notified on change, and do something with this change. All right, so why coroutines?
17:01
And especially why coroutines when we have other libraries like RxJava? So this example, it's not mine. I took it from some JetBrain slides. But it's really pinned the point that I want to make clear. We have here example of two codes that we need to write,
17:22
both of them doing the same jobs. We have user, but we're going to get some credentials and do login. After we're going to do the login, we're going to get a user ID. And with the user ID, we're going to load some user data. And we're going to show it. So in the Rx, like in the left side,
17:43
we're going to declare the login as a single and the load user data as a single. And spoiler, every time that you're going to use single, you need to think maybe it's not the right tool for what you need to do, because in this
18:03
case, all we need to do is just to synchronize between things. So the code looks like that. We have the login with conditional flat map to load the user data. On success, we're going to show the data. We need to subscribe on that. On the right side, it's not just the right side.
18:21
It's actually the right side. So everything is going to be sequenced, because like I described it to you, I'm just going to do the login, get the user data, and show it. And it's much more readable, at least for me.
18:42
Beside that, why to use coroutines? So the code inside the coroutines can be expressed sequentially. I can build my own abstraction. It's very easy to do it. So the code will be very readable for me and my team. And every new guy that will come to the team,
19:02
it will be very easy for him to jump in and start coding. There is a lot of integration with other major frameworks that's coming out of the box from JetBrain, like with RX1 and RX2 and Java 8 streams.
19:21
And it's very easy to write additional one, if you want. And we have some talks about multiplatform. And JetBrain, indeed, working on porting the coroutines to other platforms. So there is, right now, I think, already implementation for JavaScript.
19:41
And they're working also on native. So in a world of multiplatform, it could be a very nice way to do async code in the common libraries that we're going to have. And with that, I want to thank you. If you have any questions, I'm going to be around. And thanks a lot.