KotlinConf 2018 - Android Suspenders by Chris Banes

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] [Applause] [Music] hello my name is Chris Bane's and I'm an engineer at the developer relations team at Google I'm here today to speak to you about carotenes and specifically about using them on android in your apps so before we get started this these slides are already available at this link so there's quite a few like URLs and slides so don't be worried about trying to get them you can get them later from here so Android like most of our UI frameworks out there has a you are you Fred it has its single threaded and it expects nearly all of its UI updates to be done on that fret so if things includes the flight view inflation you can do stuff off thread and we actually provide something called a single hand inflator but it has sort of certain downsides to it and it doesn't work in all cases stuff like measure and layout and then the bulk your app when he's doing when he's actually on screen he's doing measuring layout so if you changed some text on display has to go for a Hall Pass drawing a lot of drone is still done the main thread although some of it is done off right now so we have things like render thread for certain animations and many many more things so all of your app code at least the entry points are mainly done on the Android main thread and so when we think about devices these days the refresh rate on most device in your pocket will be 60 Hertz and that gives you 16 milliseconds or near enough to actually make sure you update the UI and so it doesn't jack but more more devices these days are that refresh rate is getting higher and higher so in a 90 Hertz device you've actually got 12 milliseconds to did the same amount of work and again 120 Hertz the razor phone was released early last year at the sheraton and that 120 Hertz I gave you eight milliseconds to do the exact same at work but obviously in half the time now if you look at systrace you'll find that most of the things we just like take them out of time it tends to be app code or at least stuff that your app calls like a low inflation layout measure all that kind of stuff so how many fix it well I think you should I think lay up all the things most phones in your pockets will have four Plus cause so we should use them get off that main thread as much as possible so that anything that doesn't need to be on it shouldn't be on it now this talk isn't about trying to hit eight millisecond refirm boundaries but it gives you an idea of the kind of problems that we as app developers face that we can actually fix with tools so how do we do that so in the past we've give you a multitude of ways to do a sink Basin task back in cupcake came out is it like a simple abstraction over threads and unfortunately has all the issues we all know about especially regarding rotation then we have executors that executors which async tasks you said sort of be a lever above now Fred's in cells when you create a new Fred it takes about a megabyte of RAM or similar anyway so Fred's a heavy - great so we should reuse them and that's as executors do unfortunately the actual API to run stuff is pretty basic run and that's what you get loaders were added to fragments there about three or four years ago and they're a nice way to load data into your UI now there it was a nice start but it's now deprecated and the reason why is because it only really supported content providers and content your eyes and most developers don't use those and we know that futures so unless your Minister k24 you can't use completable future and a next one during pull out of guava you can't use it stuff so again not really what's me want use libraries and so I remember days when we had stuff like Robo spice like I don't have six seven years ago and then we actually shipped what shipped volley and we actually gave you the source but didn't give you a job but then the community is actually rallied around themselves and creative things that are xjv so actually do this stuff and they're great like we're not trying to advocate against using them it's just their alternatives so hopefully if you're in this talk you want to know more about coyotes so I'm not going to go too much of that so why actually use them so when I think about this question when I was writing these slides I'll swing it what does your typical mobile application do and for the most part they're crude apps and I don't mean the American Slang I mean the fact they create they read they update and they delete it's all about data on screen or locally stored and then they might do some sinking so then what citizen webservice and then back down sir so they're free simple up surety when you think about it there's nothing difficult in your typical app they're not something really massively computationally complex it tends to be tricky just getting too tricky to get the stuff right rather than it being super computational and of course there's a lot stuff in and whoever gets in the way it makes your job harder so why carotenes well I think they're great for UI tasks and that's because they scale really well on oozes hardware we're saying oh yeah Fred takes about a megabyte of RAM your typical carotene Aparri in the tens of kilobytes so it's a lot less and the other thing that I think is better is that they have a much easier development model so instead of having to write something like an rx chain which is if someone doesn't know rx and they come in to look at your co source it's kind of hard to read so write an imperative economy's sequential code you know stuff we've been doomed since anyone learn Java and is much easier to read so this is the app I've been playing around with for the past 12 months it's been my playground for a lot of stuff when I first started writing this app it was completely rx driver and I've kind of moved over past six months to using carotenes and so terrific about half and half now anyway it works and so a lot the examples I use in the rest of the presentations taken from here one thing I'd like to note in this presentation is that this isn't an official Google endorsement for co-routines or anything like that this has been my experiment at what some of the equipment and sort of things I've come out of it with and so I feel like carotenes is still obviously a very young technology and so we all need to come together as a community to actually work out what's a best practice and anyone who says that they know the best practice of an enrollment is probably lying so one of the things I was looking at with the coatings is actual apk size so that's a thing Android developers have to worry about a lot and so in my app I use free libraries core which contains the majority of what you're going to need in carotenes Android which is contains the Android main dispatcher and then our x2 which is a really nice library where you can run it interrupt between iris Java and coyotes so when you actually look at the jar size if you just download mothma central it's about 774 kilobytes which actually quite large I was surprised by that but when you actually sort of drag it into your apk and you look at it it actually comes down to about 500 kilobytes that's pretty packed in so it's actually not too bad but as soon as you start turning on minify so this is minify without optimization so it's just tree shaking basically and that comes down really nicely down 213 kilobytes and in my up I'm actual using quite a large portion of the API so if you're not using that much of carotenes that will contain you further and if you turn on optimization it comes down even further less than 100k so hope you can see that actually include it in your app isn't a really a cost insert in terms of api's apk size anyway I one thing to note about using ProGuard or r8 with this is that you need to include this rule and this has been just taken from the github Curtin's github repository it probably gonna be updated over the day but so hope you buy now your finger mat actually using carotenes in your app so let's now talk about how you actually write them and actually use it so here's the suspending function from the app it's actually from the data repository a lot of data lie over in the app and it's given a show ID and it updates the show it's pretty simple really and the first thing it does is that it gets from the local store so the local database then it will get it from a remote source the first one and then from a second remote source it does not emerge and then finally or emerge that all the results together and then save it to the database again so pretty simple method really but if you look at this free tasks nonde free tasks have dependencies on each other you know they could all be done concurrently so why don't we do that and cursings makes it really easy we just wrap each of them in a nascent block and we sort of keep holder it deferred just get for the second get for the third and then finally we can just await the result of each and now all that all the three of those tasks atom being run concurrently and it takes this time so that's just a small example of how Curtin have casually helped you so how do you actually use them on Android well here's the simple example I could come up with and it's a very simple view model the first thing that's half they're just actually be able to get access this chair repository don't really care about this at this point and now in most of my V models I have an inner block and in their idea of refresh every time it feels kind of right that every time you crave your model so each time so it's come to screen we actually do a refresh because the user always wants update information and in that refresh method we will launch a curtain and that will just literally call our update show method yeah pretty simple really but can even see the issue here what happens if the v-model goes away before that launch is actually finished we need a way to actually cancel and that's where jobs come in now what actually happens when you call launched is that it will return a job and that job allows you to then cancel it so we actually have a way to actually call back that parity and cancel it while it's being run obviously wouldn't do it here because we wouldn't just launch something an industry-wide cancel it's just an example to show you that the method is this and really we want to do it in whenever the view model is sort of destroyed and that is in the uncounseled unclear method so what we do here is that we would keep a reference to the job and so we in the property would have updates show job and then we update it so every time we call lunch we keep reference to the job and then finally an uncleared we just call counsel but the problem with that is that the using the pattern is called for pain because more likely you're going to have more than one launch happening or a sink or whatever it is so you're not just gonna have one finger at a time you only have three or four or five so if we have to we don't have to keep track of two jobs if you have free we have to keep track of three jobs it gets a bit unwieldy and a bit too much code to actually maintain arexx Java goes kind of difference what carotenes does rxjava gives you composite drawable so you have a collection of things so instead of having those free properties at the top you'd give it a collection and you'd give it a night into a collection Kerry teary co-routines goes a different way and the curtains can have a parent-child relationship and that allows us to track or encourage the curtains are being run and finally counselling has needed an access for assets expressive ear jobs within a context it's a critical class in the courtesies library as it represents sort of a bunch of work the track so if we go back to our view model how do we make use of this satchel a lot parental relationship and what is this so the first thing we can do is create a job and that is created whenever they've email is created and because we want it to last the same lifetime as the view model and this will be the thing that we actually set as the parent of the curtains and then in the launch will actually give it the job as the parent of the launch once we pass that the value that the job becomes the parent and finally uncleared we've actually cuts the job all that means is the launch that launch of curtained is actually scoped to the lifetime of the movie model and one thing to note here is that as soon as that job is cancelled you can no longer use that job again it's terminal it's dead that brings us quite closely onto the subject of scopes and scope and carotenes to kind of a lifetime of things have something the problem with the example I just gave is that you have to be very explicit over actually the parent and it's very easy to forget to actually pass those parents down so if we go for an example here so here we have and we have our launch in the inner kind of method within it just like in line and you can see that that outer co-routine is actually running at this point but right seat we're at the yellow arrow so we have initially entered the significant so we'll go past the first async and we've suddenly got a second at first clip on an inner current even for now and then we'll do the second and that's also a separate curve another carotene and these are new car routines which are running at this point we'll keep going and then we'll hit the first await now at this point that outer carotene is suspended waiting for the result of the first carotene to actually finish but during this time the v-model is gone it gets destroyed for whatever reason the screen goes off screen which means that the job is canceled remember we had that job and we cancelled it but hope you can see that those two inner curry teas are still running because they haven't been canceled and the reason for that is because there is no parent set so we are the parent on the outer carotene but it doesn't actually automatically populate down to the inner ones now all the code I've just been talking about for the past ten minutes is actually defecated the reason I've taught a lot spoken about it is that it evens stuff that's in 26 and above uses this stuff internally so it's important to know how it kind of works and also you have legacy code it's important to know why you want to move so the new hotness is curtain co-routine scope and its new interface in version 2012 0.26 so that was three weeks ago which allows objects to provide a scope for carotenes so the perfect example this is like activities and fragments and services because they have a lifecycle themselves we want the curtain let's get launched to have the same life cycle now async and launch actually become instant methods on top of your scope well not not quite into methods their default default methods the interface but it goes from launching and building a KO routine just globally to instead in mentally anyway you running the curry teen on X so I'm gonna launch my career teen on my activity which means it just makes it easier in my mind that you're scoping some into the lifetime or something and you also get to provide a default context and that allows us to actually sleep in that job objects and so it comes our way down so we go back to our view model let's convert it to a carrot in scope so we'll implement the interface and then we have to give it what we have to implement the property of called carrot in context and that's that base context and that's the thing we want to the default for the Chrome for launch the new sinks here I'm using the job that we've created and also a dispatcher and I like to use dispatch domain as which is the Android main thread as the default dispatcher because I like to be explicit I think it's better to be explicit about going off Fred rather than being explicit about going on the fred will talk about my dispatchers later anyway and then if we go down to our refresh and i'll launch we can get rid of that parent because it no longer does this and that's it really if you actually look at the method semantically this is kind of what it does instead it's like this dot launch and it kind of points to the context that we have up there so it's not exactly what happens but semantically in my mind for that watch what happens there's more layering involved but that's what it's quite easy to have strats all the code I just mentioned in someone I felt based class it's pretty simple there's not much to do you can do the same thing for activity and fragment it just change them so if we go back to our example we also have to modify the suspending functions because as we mentioned launched and async and our instant methods on the on a scope because we don't have a scope in this method we need to get one luckily there is a nice utility method called coding scope and that will create a new scope for us based on the one we're currently running in which basically means that we get access to 18 can launch and all that all over again and once we use that icing builds are actually scoped to the other care team so we give the example again we're running arrow to how's the care routine at the top still if we go for us the first one async to run in and seconds icing string but these are now more child care routines to have a parent or relationship is actually there and again we're going to wait for your weight and which point the outer carotene suspended again the v-model goes away so we get canceled but you can see that those inner co-routines have also been paused or canceled partially in see that the children are canceled too so if you can see some of the benefits you get from the scoping and like the implicit functionality that we get now in the new version of the carotenes likely but what if you're not using few models now we know they some of you who aren't using view models or majority of you may not be now as i mentioned earlier you can also create a basic class for fragment again very very similar to the view model just different base class but also as part of the android architecture components which we released 18 months ago we have now life cycles so we allow you to observe life cycles which tends to be much easier and so we no longer have to use a user's base classes so here's a very small example you create an observer and you can implement in the methods there's seven methods create start start pause and blah blah here which using create and destroy and then once you want to use them you just life cycle add observer you know that's just a small context for how you using so if you actually wanna create a life cycle coating scope let's build on so we'll create the classes scatters in class and anything really care about as before is on destroy because we want to know when to cancel that job so we'll create a job in the instance and then we'll cancel it pretty simple again and again implement the interface and then create a context so this code repeated many many times you can see it's cuz this pretty much the basis of all you have to do and to actually use this is like this so you create a instance of that class we're gonna use a fragment here but it could be anything really that exposes a lifecycle and you add the observer and then to actually launch something can occur eighteen you have to call scope dot and then the actual launch is described to the lifecycle of the fragment now if you're using either know a life cycle of service it'll be the same thing life cycles as lot axes like a really nice intermediary seeking decoupled a lot of code Louie actually wrote a really nice implementation this I mean the information implementation we just went through is pretty simple but Louise one is actually much more complex and you can actually do a lot more magic in it so I really go and look at if you need to use system so we talked a lot about canceling jobs and every teens in general but should also talk a little bit about how it works so cancellation requires cooperation so it's kind of bit like Fred interrupts in a way but it's a bit more less specifically it needs you need to check in your code whether you have been canceled well specifically you need to check you me active that's what the API is is active so there's two ways to cooperate the first is simply by calling suspending function and most of the suspending functions in the library do this kind of code it want to be exact same but it does semi similar where like it'll get the job it will check it is active it's not it off row cancellation exception and that's how the constellations build up yields a good example of how to do this and a way to delay there's a few others well you can do so long as you're calling these you're already pretty much cancellation aware alternatively if you're not calling these if any functions you can actually check the property yourself so here's this very small example where iterating through some files and then for each file we are checking with our active before we actually do some chunk of work that leads us nicely onto exceptions we just pay for exceptions and a few things that have caught me out over the past six months or so and they make up might catch you up let me not the first one is that launched kind of refroze exceptions and so it's kind of similar to a thread in that if there's an exception that happens in your launch it will get sense the default exception handler so in this example we'll go back to our update show and we're going to throw an exception at the bottom now on Android what that means is that it's treated like an uncaught exception and then your app or crashing basically the other one that caught me out a few times is that async doesn't actually throw until you call the weight and you'll hold on to the exception basically so back to our example and this time we're gonna throw the exception in the first async block now here obviously just said it's not actually throw until your weight the thing that Casson caught me out here actually is that I was accidentally using async instead of a launch and so I was actually using async it's like a foreign forget type thing which basically means that a single then swallow the exception because I was never calling a weight and so it's just me to sort of be aware of and the way you actually handle this stuff is standard try-catch so it just taught quite a lot about carotenes now and how you actually write them that it's important to know how they actually run in your device and that's where dispatchers come in so launch and it's async and all those builders have a context and the default one uses dispatches default so what is a natural dispatcher well it's the thing which runs and schedules your carotenes it's the actual magic Valentin's a lot of time and the default dispatcher which is called default dispatches default actually equals internally something called default scheduler that is new in having it was a V 2025 it's only become the default in V 0.30 but it's it uses CPU camp fred's and so whatever your new max if you have any other device that's how many of my own friends parallelism and parallelism that that schedule can use with a minimum of two so you'll always have at least two as part of 0.25 a new dispatcher was added called IO and that's specifically designed for blocking i/o tasks so this is the one which under developers really should be caring about it's a lot of the time you're going to be doing network disk you know all that kind of stuff so this is the one where you should be using quite a lot and it instead of you using the CPU account it has a minimum parallelism of 64 so you can have up to 64 things running at a time which you think sounds like a lot of it when you think things uploading images on a grid or I don't know whatever it is there's actually that's a good number the really great thing about the i/o dispatcher and d4 scheduler is that they I Oh uses default schedule 8 internally so they actually share threads so you look at this example here we are loschen async using the default dispatcher and we'll immediately switch to the i/o dispatcher and then load an image we're doing some disk reading after that we're going to do some processing of some kind but we rely on doing that on a default dispatcher now because they kind of share threads you can it may not actually switch your fred's around say there's no fred concepts which we just take them the time anyway so if you cuz the share Fred's you may not actually switch Fred's and then just wait and save time so reactivity how many blog posts can be summarized as this slide lately anyway and I myself have been one of those people so like I'm gonna make the premise that most developers at least get into using Irish drivers just for the easy Freddy model they I'm not saying everyone but I'll say a lot of people use it just because they want to use schedule on and observe on to easily switch friends and I'd say that a lot of them end up mainly using single maybe in completable like just a single value types now they only exist in our Java Eric Scala and arts groovy so we're not spin about this is that maybe that's more reflection of sort of platform API so they've run on is they're all pretty similar rather than the fact that need to live in rx so it's thinking that Korea teens can actually quite easily replace single maybe in command but completable but actually you actually get all the benefits occurring it's like the imperative style so if we go for an example here's a retrofit example which everyone likes to use and we're gonna have an endpoint called trending shows and that returns a single of a list and the way you'd actually consume that on RX Java is you'd do schedule on switch on to some kind of scheduler and then subscribe yeah standard rx now you can actually consume that single for quite nicely in a securities so there's a lot of nice extension functions within the rx - extension library on currencies which allow you to kind of easily convert them so there's a nice one called a weight which is on single and convertible and maybe and so you can just actually consume them completely in carotenes which is really nice when you're actually already using currency arts Java and you've want to kind of switch over to carotenes so you can do that kind of step by step gradual migration if you want to use in these expansion functions now wouldn't be nice instead of actually return a single that we could just make that retrofit interface just a suspending function now this is actually coming soon to retrofit someone to Jake earlier and he said they just went from one lot free to come out and then that we're gonna release it so that's pretty cool which means that our calling code literature comes from that down to that we remove the away because we no longer need it it's just a standard suspending function and it looks great and it brings on two channels now the one thing to be aware about channels is this this still experimental and they kind of fit they kind of feel like they need a bit more so I feel like they're not really a replacement for our establishes and the closest thing there probably are is just a flowable because they have back pressure support but they feel close to me like a kind of suspending DQ which I guess that's what's that they're actually called the docks but they still kind of describe an async stream of things channels call these elements are historical items it's just semantics yeah so we go for a quick example of how you can actually send and consume them so we'll create a channel and this time this channel is gonna be of a bitmap our first one is I send inside so this we're gonna launch carotene and that is gonna sort of iterate for a list of files and then just load them it's gonna read them discs and then send them to the channel it's pretty simple really and finally once we've consumed all of those files we need to actually close the channel it's actually explicity so channel that close once that done we can actually launch another carotene to actually consume them now in here that it's in the same file as such but there could be a two ends loop your app it doesn't have to be the same thing but here going to launch it and then we're gonna use the consume each method that does it opens a subscription onto the channel and then consumes it and compute confuse each item until it's been closed and this point we can do other stuff um process it so that's a super trivial example of a channel and now we're comparing channels to our extravaganza have similar things a lot broadcast channels are very similar to a subject conflated broadcast channel is a special version which is also kind of similar to a behavior subject so if you're using them you can kind of swap over some small differences channels actually support null values whereas artist Java if you send it no value it'll crash and channels only support hot streams so that's a bit of a fundamental difference instead of operators and so Irish driver is known for its many many operas and channels has roughly 73 from my count yesterday built-in operators and a lot of them are kind of collection type methods if you compare that cyrix Java it has many about 230 from my count yesterday now a lot of them are not really applicable to Kotlin because some of them like function 0 where you don't really need them when you have a lambda and it also comes a downside when you have so many operators I find and I found when I was no novice driver that the more operators there are there's that it's just someone will do trying to learn it all and so actually doesn't come with a complexity which is hard for beginners to learn one thing that I like with carotenes anyway is that these operators are actually nicer to write so if you if you were to write an artist Java operator it's actually quite a lot of work but in kirti's it's a lot easier so here we're going to write an extension function on publisher which implements a map obviously publisher already has a map about what these flower borders so if we actually implement it in curry teens and inform behaviors that literally just give it a map which is a lambda and then it consumes each maps it and then sends it straight back so it's pretty simple really so my final section in this presentation is about actually converting android api to using carotenes and so there's two examples i have two examples which i'm sure a lot of apps actually use the first is actually getting the last known location in this example we're going to use the fused location provider client api which is a very bad name but it's in goo play services and it's actually quite nice api in that it kind of merges together all of the different location providers for you into a nice single result so GPS Wi-Fi network bluetooth all those it merges together and handles it all nicely the display services it actually returns what they call a task which is for all intents and purposes a future so the APR we're going to use is called client I'll get like get last location and that returns a task which as a result type of a location and here's how you'd use it normally without carotenes you just add a non complete listener and then as soon as the actual task is completed you will get the result back so if you think what we're actually doing here we are converting a callback api into a suspending function that's pretty much what you want and look at the the carotenes library gives us a real nice way of doing that called suspend carotene and what suspend carotene does is that you give it a lambda and it expects in that lambda that you will settle your callback and actually sort of trigger the task and so it actually starts working at that point it will immediately suspend waiting for that callback to come you were given a continuation object and that is how you later tell the carotene to start running again with the result so if you actually start right in our skeleton method we're gonna have a suspending function called get last location and that returns a location object like a natural straight-up location we're gonna use suspend carotene straight away and then we have that continuation object passed to us at this point we can actually call the google play services api and the important thing here is that we callbacks are continuations so that the curry teen actually finishes and gets a result and that is done here the continuation don't resume so we actually get the result and just pass it back via the resume method now since Play services is a task so it's basically a future it doesn't just handle the success case it handles the failure case too so that means that we can bubble up there any exceptions that happen into the UPS of the carrot into and we do that via the resume with exception method instead it doesn't have to be for any sort kind of callback if you have it and just run into reception when you're was in the code you can also do it with resume exception too so that's the con is more simple example which is actually just getting one one call back in one value back and then there's a spending function but most of time you might actually want to observe location updates to you think like Maps or I don't know a navigation app you might actually observe the locations so that you always updated when the location changes and that is kind of like a multiband you call back again we're going to be using fused location provider client a different API this time called request location updates so it's slightly different to the first API in that you request and then you'll give it a callback and that's the callback you all tell you if any locations change if you look the API there's three parameters the first one is put a location request and that is how you define sort location parameters so I don't know you want to only be notified when location changes by 10 meters or something second you have the callback which is how what will be called back on and then thirdly we have the looper and that is the looper or the thread in Android what kind of thread and and returns that your callback will be actually invoked on and as mentioned earlier you call the API to start location updates but you also need to tell it to stop and that is done via remove location updates so that you can live free of GPS and you know just can't even kill in the users battery so let's build our function has to spend him what not suspended function I've carotene function so we'll create a function called observed location and we still want to pass in the location request because we still want the caller to be able to define that stuff but this time we'll return a receive channel an actual channel of locations so let's build a function so we got this and there we will create a channel straight away that's the thing that can be returned and so that the caller can actually observe it now we're going to create our callback the Play services stuff we create it as an instance because of the way the Play services API works in that you need to give it the original callback to stop it all at the end so we need to sort of save it as a value involving here is that once we have a value and once the callback is invoked we just send it straight to the channel and we will use same block in here the reason you send blocking is that because we're running the carotene we don't really care what Freddie's been running we're just happy to run whatever carotene red ring so at this point we actually need to start kicking off the location update so we'll just call request location updates with our requests now callback and because as I said we were happy with what I've referred to run on which is loop it on my looper which the utility method it in Android which is just basically whatever looper is currently in my friend one of the neat things you can do is you can actually add listener on to channels which can which ring votes whenever they're closed what this allows us do is actually neatly what quite anything anyway hook in to the channel and so that we can automatically tell play services to stop doing location updates automatically and that's the sec what we do and we invoke enclose then which call remove location updates so if we actually look at how to use this kind of method that we just created so we'll create it will store from the location channel and it's probably nice that we're doing this but it's why I came up with this morning and then an on start will actually start trying to observe that location and so in this request we're just doing a minimum displacement 100 meters you know whatever it is the important thing is that we call observer location and then we store that channel at this point we can actually start using the channel and so we'll launch of carotene and then we'll call consume each which is the method we sort of showed earlier and then that will be invoked recursively when they're an item is emitted emitted iris gemstone but sent into the channel and then on stop because we need to tell the channel to actually close we'll call cancel and because we have that invoke on clothes listener that we spoke about earlier and it will automatically Bob loves to their play services api and tell it to stop observing locations alright so that's it if you want some information on what's next we actually launched a code lab two days ago which is all about you can carry teens in your app so check that out also read the manual the guides are really really good they're very used case driven and so they're all about a use case and then the easy Matic way in courage needs to do it and so make sure you read them all and thirdly there are free great talks happening tomorrow all about go routines make sure you attend them that is me thank you very much [Applause]
Info
Channel: JetBrainsTV
Views: 15,160
Rating: 4.9897437 out of 5
Keywords: JetBrains, kotlinconf, kotlinconf18, kotlinconf 2018, chris banes, rxjava, java, coroutines, android
Id: P7ov_r1JZ1g
Channel Id: undefined
Length: 38min 28sec (2308 seconds)
Published: Fri Oct 12 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.