Understand Kotlin Coroutines on Android (Google I/O'19)

[Music] my name is Igor I'm an engineer in the Android team hey my name is Sergey and I work in the same team hi I'm Sean I'm from Android dev rel okay today we are going to talk about qualities on androids but before we talk about that let's try to figure out like why do you even need some qualities and to understand that let's get how you write UI code on Android so this is the dream code we want to write you like color function makes a network request whatever you get the user set it on the text view this is what you want to write but if you write that you get an exception because you cannot make a network request on the main thread easy we just put an insider threat and run the code now you're going to complain is the text view which is like you cannot touch the UI thread from a background thread and okay go write this kind of code where you make it a synchronous you provide a callback it runs on the background thread and cause your callback on the UI thread and this code works fine except if you're writing code like this you're gonna receive out of out of memory exceptions because you're going to be leaking those callbacks left and right there's a solution to this as well where we can like have an understanding of a subscription that keeps his chain and whenever you're a stop we can just cancel the subscription that one works but then you end up something like this and I'm not making this up by the way three years ago when we started architecture components I was looking at the Google app code and I found one application that had 26 lines of amount math unregister so if it happens at Google it happens everywhere like we'd always write the base code but there's a solution to this as well which if you are using something like Eric's Java and so this function returns an observable you can just use the auto dispose library associated subscription with your real life cycle and safely subscribe and this works perfectly similarly you could be using live data which enforces you to have a life cycle to observe and this also works so this whole problem right why are we even talking here about this if there's already good solutions every year we run these developer benchmark service we ask the voters how are they doing but are there problems and the service we run last year one of the top cop plays was threading and concurrency developer says this is hard on Android and one of the top Lucas was this week what we call live data plus once the people want us to extend live data make it more like Eric's Chara and you're like why why do you want this we have good solutions just use one of them so we didn't bother we do best when we don't know we did a UX research this is a user experience research on concurrency so we did in-depth interviews with nine developers what they do is they do the regular work for a couple of weeks in their own company and every time they see a problem about concurrency like this observable T they just write it down this was the problem this is I sold it and this is how I feel about it and in this study we focused on three main things focused on live data which is our observable date older we focused on Eric Shaw with the reactive Sims library and qualities which provides suspend of all computations and in the result of that study this was the conclusion for live data people say we love it but we want a complete solution in fact is funny live data doesn't even support anything but a main thread but we talked about it in a concurrent study for our Xterra it is amazing people love and hate it they love how powerful it is but the common complaint we always heard was well as always misused it feels like an overkill and for qualities this was like it really looks like the best solution but I'm not sure it's very new it's not major so this was the overall conclusion we said we need a solution that is simple it shouldn't be hard to learn that solution it should be compressive so you should be able to scale to different use cases and it should be robots that should be built in testing stories so made two decisions we said okay we are going to have first-class quality support in jetpack and we are going to have more support for rxjava in our documentation but today it is all about Cortese so shut why don't you tell us a little bit more about what qualities are thanks you so I want to just take five minutes and talk a little bit about what problem carotenes are great at solving so in a sentence the main problem the correcting solve is simplifying async programming by replacing callbacks which is quite abstract so let's look at some samples and see what that looks like I'm gonna make an imaginary Network request three ways the first style is what's called a blocking style this is where the result is returned directly from the function let's see how that executes and for fun I'm gonna run that on the main thread when called a blocking network Hall will block the thread that called it so the entire time that Network request is running the main thread will be blocked and that's the thread that has to update the UI and handle user touches so the user will see your app is frozen or it might even crash now I do want to pause and say there's nothing wrong with a blocking style of network api's but it's not what we want to do on android so to fix that as he'd already talked about we commonly introduce callbacks so let's see how bad executes we're still gonna call this from the main thread but now when fetch user is called the main thread is free to perform other work it can handle on draw or respond to user touches and the networking library is responsible for finding another thread to actually run the request when the result is ready the network library can then use with a callback I gave it to callback into my code and let me know that it's ready let's rewrite that exact same code with co-routines it looks just like the blocking style the result of fetch user is available immediately and I don't have to introduce a callback to tell Collin I want to keep this with co-routines it has a suspend modifier on the function and when we run it still on the main thread the main thread is unblocked just like with callbacks and this is a key concept of carotenes the networking request still runs on another thread when the result is ready it resumes the curry team where it left off this code is much simpler than the callback style while still ensuring that I can write my Android app and make it never freeze for the user this is the key mechanism here of correcting suspend and resume when a KO routine is suspended it's not running it's paused and when it resumes it picks up from where it left off you can think of suspending ko routine as taking a callback from the rest of the function so you've put it together suspend and resume replaced callbacks we can even visualize that the callback version and the co-routine version execute almost exactly the same way let's switch back and take a look at fetch user how can we call a function that makes a network request from the main thread to start we'll need to make fetch user another suspending function this tells Kotlin that it works with care routines and inside will call another suspending function called with context well pass it dispatchers done I am zooming in on those dispatchers calling gives us three dispatchers default IO and main and they're used for different things default should be used for CPU intensive work things like transforming a list of a hundred elements calling des futile or pre computing text anything that takes too long to run on the main thread should run on the default dispatcher IO is a dispatcher that's optimized for blocking Network in disk IO you should use it anytime you need to write code that blocks an API like writing a file or reading from a socket and main this is the main thread on Android and surprisingly it's our recommendation as the right place to start co-routines in response to UI events since you're usually starting para teens from the main thread staying there will avoid extra work for simple operations then when you need to transform a list or write a file co-routines let you switch to one of the other dispatchers by using with context with context will run the block that you pass it on the dispatcher you tell it to so this block here is gonna run on dispatchers dot IO and I'm free to make blocking Network calls this allows us to provide main safe api's you can just make a function that reads and writes from the network like this and call it from the main thread and this is a huge benefit on Android now I don't have to worry about what every single function what thread it needs to run on instead I can just call it and the function itself can ensure that it's safe to be called from the main thread to finish up introducing pair routines let's take a look at how Kotlin implements them every thread has a call stack it's what you see in the debugger or a stack trace it's how collin keeps track of which function is running and its local variables when you call us to spend function Collin needs to keep track of the fact it's running a KO routine instead of a regular function I'm gonna represent this as a suspend marker everything above the suspend marker will be a couraging and everything below will be a regular function then Kotlin calls load user just like a normal function it's gonna put a stack entry onto the call stack and this is where any local variables for load user would be stored and then it just executes until it finds another suspend function call now Kotlin has to implement suspend how does it do that what's kind of simple once you figure it out all Kotlin has to do is copy the state of the function from the stack to a place where it can save it for later it'll put all suspended coverage scenes out here and it's not structured like a stack then Collin will actually call fetch user creates another stack entry and when it calls with context suspends that as well so at this point so at this point all of the curry teams on the main thread are suspended and this means the main thread is free to do other work like handle on draw or respond to user touches and this is really really important when all of the curry teens on a thread are suspended the thread is free to do other work if we fast forward a few seconds the network result will be ready and Colin will have to call resume in order to do that it just takes the save state and copies it backs over puts it on the stack and resumes the function when it resumes load user it'll just go ahead and continue executing just like normal load user head aired it would have thrown an exception right there the suspend and resume mechanism is the magic behind co-routines and we wanted to show it to you so you could understand how they work as you start using them in your code that wraps up the core routines intro hurry teams on android offer us the ability to simplify our code by replacing callbacks and allow us the ability to create main safety to ensure we never block the main thread now I'm going to hand it over to Sergey who will talk a bit about libraries you can use today with heritance thanks John yeah those very cool bits on ice but we really want to benefit in our real application from it and despite a very young age of curtains there are libraries that already support them in very stable orbit artifacts and I want to start with work manager that is Payoneer for us in Android X because it's already supports car scenes in its stable release and you can use this curtain worker but let's make a step back and try to figure out why we do that we use it so this is a typical flow of workers so if you are not familiar of work manager you can think of worker just something that does long pink round job it may have some constraints but it's very simple just some work and typical use case for that you need to synchronize some local data with your web server and this flow would look like you query your new notes from your database then upload it to the web server lastly you just mark those notes is successfully singing well you see no need for curtsies well actually we didn't start to talk about translation because translation may happen tea variety reasons for example constraints for this worker aren't met anymore or user explicitly cancel this job if you provided this UI so how you would support cancellation well you can try to do something like that you try to put like every other line this if check and it starts to look silly and even more it doesn't actually work because this call which is probably most expensive call because it goes to the network and do some work there it doesn't have any consolation signal propagation because well if it was started it will run till its end no matter what and this actually will curtail worker will help us with that we didn't talk about that yet but curtains don't really don't only ruff callbacks nicely it also provides nice consolation property so every system function can be cancelled it can react on this cancellation and also it propagates to all inner calls this cancellation signal well you may say our code inside those calls are still blocking we don't benefit from that anyhow this is true however if you use room as your database solution you can mark your queries and suspense functions and then room will take care of consolation for you as well as fretting as Sean mentioned that in multiple times this thing will be main safe room takes care of reading it will run the query on a background thread then well nice our database calls are canceled now but as we discussed before the main call is this one and actually if you use retrofit you can make it suspend as well because retrofit already for suspend modifier for its Network calls and I want to highlight that a retrofit isn't part of Android X it's a just job that's done via a community Android Android a cotton community embraces college scenes and we like it at the end of a day it's less work for us so nice now this code supports constellation and it looks as as easy as it looked before so we got a constellation for free so this was a quick look on the things that were available today and it will present you a lot of new price that we just made so so far we talked about what you could do with qualities and for the rest part of this talk we are going to talk about new stuff so first one is live data and qualities now just to be very clear live data is not designed for concurrency it's an observable value holder and you are expected to be able to access the value from the main thread that's like intentional but that doesn't mean it should not be interoperable so this is what we are going to provide you today there will be easy way to use live data with Cortese so the most common use case is you have some value you want to compute in a quarantine but smart to serve the result is a live data so to turn starting today with the lifecycles 2.

2 of a one artifact you get this new one new API called live data so it's a builder function very similar to the sickness wielders in Cortland inside that you pass a quality block and he said you could do whatever you want and call this amid function to dispatch values so if you look at this data with load function it is HLS husband function and because you are calling the image with a user in this case we can infer that type for you so you don't even need to specify this so this very simple light that the API bridges the gap between your live data elements and your qualities so you get that ap a little bit more in detail so it receives three parameters and the first one is a contact so why do we need a context well if this data is loaded user function is not less than the sass file function which is a regular function and you write this code you are going to receive an eye on main thread exception because this vlog by default resume dispatchers main but you can change that we can tell it give it a context as dispatchers IO and now this code will work perfectly I want you to notice that I didn't change any contents of the code because you can omit from whatever dispatcher you want you don't need to be on the main dispatcher to change the values now the second one is a really awkward parameter called timeout and to understand why we needed a timeout parameter let's look at the a famous rotation problem on Android so on the Left I have a view model that serves a live data and on the right I have an activity that's observing it so when my activity goes to started state the live data will become active which means ok you're an observer visible to the user you are better off creating some values but during that time what if our activity rotates and so it's going to be stopped my data will become inactive be destroyed and a new activity will come so right now there is no one observing live data so there's no reason to produce results except after the new one goes started again it becomes active again so the problem we are trying to solve here is this gap where live data quickly becomes inactive and active in a very quick succession like usual less than one second so how do we fix that let's look at the detail how we run that code block and to understand it better we're just going to write a timer function it's basically creates a timer for live data it gets the current time returns a live data builder and in an infinite loop it just missed the time the last one second a miss the time delays one second and never ends and this code I'm showing is hundred percent okay to write how does it actually work when the live data returned by this blog becomes active we check okay did we run this block and if we didn't run that block now we start executing it while we're executing it if that block becomes inactive like if the live data becomes inactive we check okay is this block still running and if it is still running we give it some time to finish but even after the timeout if it is still running and we are inactive that's basically unnecessary computation there is no one observing the live data but the block keeps running so we just cancel the continuation the recording so if the like that' becomes active again we're just going to restart it and we only do it once so if it finished the completion there is no reason to restart it now you can also emit more than one value so this was some structure around this sample we had before where we have a repository that has a get user function a lot user function and the loss from the database and a Miss Tattaglia now most of the time this is not the code you write you need to go to the web service fetch an updated user update the database and omit that value again so you could call emit as many times as you want as long as you're inside that quarantine block but you might say well most of the time the database doesn't return you a user it returns your live data for user because you want to be notified about the changes well all you can say is you could just call a meat source if you ever used mediator live data this is very similar to at source where it says whatever value comes from the slide data just make it my value and you can run things like transformations here oh also now we don't need this extra emit because we already observing the database so you can get rid of it so this slide at the API basically provides us a very nice way to make likely to work with qualities but how what we models thank you so let's talk a little bit about how to integrate into integrate carotenes into your view models but first I'm gonna talk a little bit about Gleeks specifically co-routine leaks these are very serious problem they're kind of like a memory leak that we're all familiar with but way worse a cover team can resume itself and in addition to using memory it can use CPU it could write a file it could make a network request that doesn't need to happen to help us deal with care routine leaks Kotlin introduced this idea of co-routine scopes so what is this scope well it's really just a way of keeping track of your co-routines all care routines must run in a scope and a scope gets the ability to cancel all of the carotenes inside of it in addition they're also the place that uncaught exceptions formica routine gets shuffled off to now you put that all together and you can use scopes to help ensure that you never leaked occur routine work manager that Sergei talked about provides a scope so does the live data builder that yet just talked about view model scope is a scope it's an extension property on view model from the KTX library I'm gonna do another one of those scary infinite loop things that Yeats showed but this time in a quarantine that I start myself interview model it uses view model scope to launch a quarantine in the scope and by default this launches on main then it starts an infinite loop that doesn't know how to stop itself and every second it's gonna go ahead and write a file now that's pretty expensive it's cover teams don't make writing files faster cheaper and we definitely don't want to leak this work human scope lets us write code like this safely when the user navigates away from the screen the scope will be canceled which guarantees this very expensive work one week so if you model scope can help you avoid co-routine leaks by guaranteeing all your cover teens are canceled whenever a user leaves the screen I'm gonna pass it over to Sergei he's gonna talk about some other scopes writing yeah thanks Jon yep another thing that's very naturally provides scope is lifecycle because as you can say from his name something that has a start and the end and if you think you are not familiar face lifecycle owner interface you're actually are because it is your activity it is your fragment and don't forget that fragment conveniently has two different life cycles and the second one is associated with views inside of it and fortunately for me I don't have to talk about that but let's defined scope more precise there so as you know your fragments are get recreated over with do you configuration changes so it's life cycle can lifetime can be shorter it can be longer and lexical scope just mirrors that meaning that once your lifecycle owner receives destroy event lifecycle scope gets cancelled and all its energy ups are canceled as well so as you can see the recycle scope is very tightly coupled with UI and it works best in situation like that so previously you would have you do something like this when you decide to show some UI will delay and well this looks pretty simple so we can make it a bit harder and if we have a two steps it becomes to look very ugly because of this deep nesting and actually if you take a closer look you have some real issues here because this main Handler and those functions that touch you I don't really work nicely together because main handler is kind of a global scope it doesn't care about your lifecycle at all and those functions have reference to fragments or activities so if your delay is long enough you can easily leak a lot of them and receive out of memory exception well my second scope will cancel all those callbacks we are so for us is a kind of a callback because it's suspend function it will cancel it automatically once your life cycle is destroyed so this code looks nicely because it's very sequential and it is actually safer however I have to say that life cycle scope is a bit of a danger zone so let's rewind a little bit I was the one who show you that retrofit and room supports suspend functions it shows you something look that very family looks like that when you say okay I will combine those functions to network and database into some repository pattern and I'll have just one function which is suspend function that orchestrate all of this work so I just need the scope to call it so why wouldn't I just call it in my life cycle scope and it's actually not a brightest idea well why and don't get me wrong you can Sean told you everything correctly it won't lock main thread it wants the curtains however do remember this picture lexical scope get canceled on every configuration change meaning that your main that your network request gets canceled every time so it is just wasteful you're wasting users resources to battle resources it's just bad for environment so so how you would do it properly well one of the things actually was presented by get like this life that builder will work very nice in this kind of situations I'll present you like an hour way how you can approach this so your starting point from this kind of task is a view model scope so you just run this load function in this femoral scope then we introduce a function in the model that you connect our UI nav model third when you grab a node well as we discussed it's a network call somewhere inside of his load node so it's I seen Fran separation so it should be suspended and well now we need somehow connect this note that is loaded in one scope and load not functions will be called in some other scope well I will use complete deferrable well it sounds a bit scary but it's actually a very simple thing you will see in a second so how we use it we complete our deferred with a note that we loaded it just put the note into this object nothing happens and readers request the note like with a weight function from this deferred if a note isn't ready yet then the reader will be suspended if it is ready we will resumed right away so this is how implemented our review model and last step we just call that in our lifecycle scope this load note function that we introduced in a view model and our network called properly is is properly executed in view model scope so it's not affected by configuration changes and our update UI function doesn't leak once your life cycle one cycle owner gets destroyed however once we add the fragment into the picture things get complicated as always so right we decided to run the fragment transaction and you will get a legal state exception because nothing guarantees you that you are in the correct state that allows you to execute fragment transaction and we did something smart and introduced some special function that help you to deal with these kind of situations and this is going to be a bit tricky because it's actually fairly complicated things but what it does this block will run only when your application is started or resumed meaning that is enough for a ground and this block will be suspended when the application is when your life cycle is just created so let's take a look on an example what it actually means so you have this function I read it is called probably in the beginning your block will be suspended because note is not ready then once is ready in usual situation we would resume execution and proceed to the next line but with lunch when started function we're going to go and check life cycle if it is not started we going suspend forever until the life cycle will become started again and once it is started when we're going to proceed to the next line and in the execute this transaction so we won't run into this exception situation so one thing I want to highlight that this block is suspended during creation and it's a different frame from being canceled because cancellation is still provided where lifecycle scope when destroy haven't happen and now as you can see we it's something that we definitely need to test and Chango how do that thanks Sergey so we talk to you a lot about car routines today we talked about how they can help clean up api's by replacing callbacks with suspend and resume we talked about different ways they can be used in different situations and that's all great that's awesome but if they were difficult to test that just be a non-starter it wouldn't be something that I would take very seriously as a thing to use so what I want to talk to you about right now is Kotlin XK routine test it's a new library came out about a week and a half ago that's currently marked experimental car routines API because it needs it needs more feedback before it makes it all the way to the stable it's a collaboration between Google and JetBrains to make testing cover teens on Android very easy so it's not coupled to any testing libraries so you can use J unifor you can use J unit 5 you can use your own custom test runner that you've built and this library is going to help you test pair routines so I'm gonna focus in on that live data builder that each showed and we're gonna talk about how to write a test for that so I'm just gonna omit one I'm gonna wait a second and then I'm gonna admit to so this is a relatively simple live data so I can focus in on how to write the test for it so to get started we need to mock out that main dispatcher the wipe data builder uses dispatchers not mean by default which is the actual main threat on Android we can replace it with a test co-routine dispatcher this is a special dispatcher designed for testing carotenes and we can make a test correcting scope this is a scope designed for testing care routines so then in setup you can switch out dispatchers that main for a testing dispatcher this will change the global value for dispatchers that mean immediately so the live data builder will use the dispatcher we give it and then in teardown reset main to the default value and then this last line here on the bottom is really really important it says test scope cleanup test care routines if you think about what a dispatcher and a scope are doing their very stateful right they have to keep track of your Co routines and actually run them so you don't call this it's very easy to leak state between tests so that's a lot of boilerplate so you can go ahead and put that together in maybe a J unit for role this doesn't come in the library but you can write all of that code into a rule and I would expect to see a library that does this relatively shortly so whatever testing framework you're using however you should build an abstraction that's appropriate for your testing framework to do that code the rule that I'm defining here exposes test care routine scope interface which lets me call run blocking tests this is a cover team builder that's optimized for testing it works kind of like run blocking but it makes writing a lot of tests easier oh and it returns unit so you can use it in single expression style in your test then we get the subject and then we need to start observing the live data so it will execute remember the live data builder won't run until someone's observing it I'll define a little test helper called observe for testing this is just my test code it's not in a library anywhere it's just gonna start an observer and then call the block that I passed it and back to the test the first value has already been emitted because I've made everything deterministic with this this test rule that I'm using I'm gonna use fluent assertions to check that the value should equal one and then I'm going to advance the time by one second this is one of the big advantages of test cut routine dispatcher you can control virtual time so advance time by it will cause that delay to return immediately and I have control over it in my test so the second image is already done when I get to this line of code there's no need to spin and wait for a result and this test won't be flaky I can just say subject that value should equal two and if we run it we see that our test passes the test runs instantly in tape instead of taking an entire second so go check out the library be sure to file any bugs that you find it's currently marked experimental carotenes api until it's had enough feedback to elevate this table and now I'm gonna hand the mic back to you just suspend the talk thanks yeah okay so I want just need stuff what is next so today we talked about how you can already use qualities in Android eggs and other Android libraries we introduced a new live data builder that lets you integrate live data with qualities and the new life cycle scopes for your view model so Cora this goes for your via model and your life cycles now we also introduced this new functional to my started which allows you to run qualities based on your lifecycle state and last but not least we have introduced a new testing library for Cortese okay so earlier today we announced call him first and for Android X and jetpack is more like Courtis first this is a recommendation we believe qualities provides the best functionality and ease of use for concurrency on Android but we acknowledge that this is work in progress most of these libraries we own are either experimental or alpha one but we want to develop this with the community the same way we do with architecture components and other jetpack libraries so you can either join us or wait six months and then that's not using them and as part of this you will see more and more of coupling and qualities coming out of jetpack so all of these are available in lifecycle to point to offer 0 1 starting today so please take a look at it and let us know how you feel about them also we really really like Curtis thank you [Music].

Source: https://zika-news.com

See More Android: https://zika-news.com/category/android/

Hãy bình luận đầu tiên

Để lại một phản hồi

Thư điện tử của bạn sẽ không được hiện thị công khai.


*