LiveData with Coroutines and Flow (Android Dev Summit '19)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

I am definitely getting confused now. It sounds like we are kinda moving away from LiveData?

πŸ‘οΈŽ︎ 20 πŸ‘€οΈŽ︎ u/WingnutWilson πŸ“…οΈŽ︎ Oct 24 2019 πŸ—«︎ replies

As someone looking to start their Android Developer career can someone try to break this down a little for me. Should I learn Live Data? If not what is the alternative?

There are so many resources for learning Android but it seems like they all contact each other or skip something.

πŸ‘οΈŽ︎ 12 πŸ‘€οΈŽ︎ u/Developer_Shane πŸ“…οΈŽ︎ Oct 24 2019 πŸ—«︎ replies

Ok, so if I shouldn't use LiveData outside of the UI layer, what am I supposed to do?

πŸ‘οΈŽ︎ 1 πŸ‘€οΈŽ︎ u/CharaNalaar πŸ“…οΈŽ︎ Oct 24 2019 πŸ—«︎ replies

Guess android is still a fractured mess sadly. Someone should tell google so they stop doing sessions and putting out documentation that says use this stuff, only to have the community tell people who are trying to do so that it's all broken, years on.

https://developer.android.com/jetpack/docs/guide

so just straight up this thread is saying the architecture guide is sending people off the cliff.

Is there a reference sample, like even one? of how the stuff done above should be done?

πŸ‘οΈŽ︎ 1 πŸ‘€οΈŽ︎ u/codeslubber πŸ“…οΈŽ︎ Oct 31 2019 πŸ—«︎ replies
Captions
[MUSIC PLAYING] JOSE ALCERRECA: Hi there. My name is Jose Alcerreca. I'm a developer relations engineer working on Android. YIGIT BOYAR: My name is Yigit Boyar. I also work on the Android team. JOSE ALCERRECA: And today we're going to talk about LiveData again. We were here last year with a talk titled fun with LiveData. Today we're going to talk about its integration with coroutines and with Flow. So since API we've struggled to understand Android life cycles. We've all studied the diagrams and more diagrams, and then [? Fragments ?] came along and generated more diagrams. So we Android developers try to isolate ourselves from having to deal with this problem. We created a layer architecture in which only the presentation layer would know about the life cycle, and then even inside the presentation layer we found some patterns and some rules that were-- well, like for example having an object that would survive an activity or a fragment recreation. We used this idea in the view model class in architecture components. So let's look at that animation a little bit more in detail. So a view is created and a view model is obtained. So it's graded as well. It exposes one or multiple LiveDatas, and then the view subscribes to these LiveDatas via data binding or manual subscription. Then there is a configuration change-- rotation, for example. The view is destroyed, and a new instance of the view appears. It subscribes again to the LiveDatas, and the view model doesn't know what just happened. The view model doesn't even have a reference to the view. It just exposes data. So why are we talking about this? Whenever you do an operation-- and an operation can be something like fetching data from the network, preparing some text-- you have to choose the scope of this operation. This means when this operation is going to cancel. If you cancel too late, then you might be wasting the use of resources. If you cancel it too soon, you might have to restart your operation. But in a real world app-- and this is the Android Dev Summit app-- we have way more scopes than that. For example, we have a schedule screen with potentially multiple instances of the fragmented schedule, the view model that is scoped to the screen, the agenda, the info screen with fragments and view model as well. Then we have a main activity and our view model that is scoped to these activities. And then you can even have custom scopes. With the navigation component, for example, you can create a scope for a login flow or for a checkout flow. And then we even have the application scope which is a special case we'll talk about later. So there's going to be a lot of operations happening at the same time, and we have to manage their cancellation somehow. We need a way to find something that helps us structure this concurrency. YIGIT BOYAR: What would that be? This, of course, our new best friend called coroutines. Now, we don't want to go through and make an introduction to kotlin coroutines, but want to talk about why we like it for this particular use case of Android development. Briefly, there are like three main nice things. First one is it's very easy to get off the main thread. Like this is a problem, we tried to solve it like 20 different technologies of yet. With coroutines, you just launch it and then you can change your dispatcher. It's very easy to write. And while writing that, you don't need to write boilerplate code. This is probably the best thing about kotlin coroutines. You write it as if you're writing book in code, and then this is all compiler magic and becomes asynchronous. But these two are nice properties, but they're not the reasons why we like coroutines for this case. The main reason is the structured concurrency it provides. It's a little bit complicated, but basically you can consider structured concurrency as a garbage collector for your jobs. So when something doesn't need to run anymore, it is killed for you automatically. And this is why we like it. So if you like coroutines on Android, how do you launch them? Luckily, in the Jetpack components we provide scopes for related Android components. So for example, if you're in a view model and you want to run something in the scope of a view model, there's a view model scope. You could use it to launch your coroutines. Or if you're in activity or fragment, you can use the lifecycle scope which gets canceled if the lifecycle is destroyed. Now, in this lifecycle case there is one more thing. Sometimes you want to run certain operations when the lifecycle is started like a fragment transaction. For those cases, you can use this launch when started, resumed, or created methods. What this does is it throws your suspend log only when the life cycle is in the desired state or greater. And let's say you said large [INAUDIBLE] started and activity stop, we suspend that computation until the activity resumes. Now, the third layer is your application layer where things get a little bit more trickier. Assume you want to run something that's not tied to any screen. In those cases, you need to figure out whether you should be using work manager or not. And the question you ask is, does this thing has to complete? Like if you're writing a Twitter client and you need to send it to your server, please use work manager. Because if you lose that [INAUDIBLE],, that would be a shame. But if you're trying to do something where you're going to clean the local database, it's fine. You can use the application scope. Like if that doesn't complete, it's not the end of the world. You can do it next time the application starts. Now let's look at how we can use that view model scope with the LiveData. Here we have a common pattern. You have a private mutual LiveData that you provide as a public property to your UI. Inside here we launch our remodel scope, we do some suspending computation, and when the result comes we update our private mutual LiveData. And notice that I'm just setting the value there. I'm not calling post value because this runs on the main dispatcher by default. Since lifecycles 2.2, there is a much nicer way to do that pattern which is a name builder called LiveData This LiveData builder gives you a coroutine block which is like a scope for your LiveData. It starts executing when the LiveData is observed, and canceled when LiveData is not used anymore. Any inside there you can emit. Another very common pattern is imagine you have a UI where user selects some item, and then you display the contents of that. A real common way to do it is that you keep that item ID in a mutual LiveData, run a [INAUDIBLE] transformation on it. And again, for these cases you can use this LiveData builder and just do a coroutine computation. This LiveData builder also receives a coroutine context. So you can call it to maybe run on the IO by default, and it doesn't matter. You can still call emit, so you don't need to think about which thread or which dispatcher I'm on. You can always call emit. There's one more function called emit source which receives the LiveData. This basically says, well, there were this other life data dispatchers. Send them as my value. So how do we cancel them? JOSE ALCERRECA: All right. So how do you cancel a coroutine? Well, I'm not here to talk about how to actually cancel a coroutine that you just started. We've been talking about scoping our coroutines so that this is done automatically for us. There are some use cases for this, but normally you won't be canceling your own jobs. What I want to point out here is what happens if you are designing a suspend function, and that function, for example, has an infinite loop? How does kotlin know when to stop that loop? Well, turns out there's a little bit of magic here. All suspending functions in kotlinx coroutines are cancelable, and delay is one of them. So when delay is called, it's going to check if the coroutine is cancel. And if it is, it's going to stop the execution. So what happens if you're not calling any cancelable functions in your suspend function? Well, you have to cooperate. We say that cancellation is cooperative. You have to check if the coroutine is active regularly with the isactive property. So before we see some buttons, there's an important distinction that we have to make. And that's between one shot operations and operations that are going to return multiple values over time. And we're going to see this with an example from the Twitter app. So in order to load the data needed for this screen, you have to perform some one shot operations. Downloading the profile picture, the Twitter handle, the tweet itself-- because tweets are not editable in 2019, Mike? But there's another thing-- that's an interesting thing at the bottom, number of retweets and number of likes. This UI is observing a data source. Because if you keep this screen open, those numbers are going to change over time. So that's the difference. So how does a one shot operation look like in the big picture? We use LiveData between view and view model for the reasons that I explained in the introduction. And then the view model we're going to make this bridge between the LiveData world and the coroutines world of using the LiveData coroutine builder. Super easy in the repository. Because we are in suspend functions, we can just call suspend functions in the data source. We get a result. We're done. When we have to deal with multiple values, things get complicated. And we solve this-- we can solve this with LiveData. We can use LiveData beyond the view model, and our talk last year was partly about this. But LiveData was never designed as a fully fledged reactive streams builder, so it's a little bit awkward to use. So luckily, . There is a new API call Flow. flow is part of kotlin coroutines, and we're going to see some examples of some code examples, and we're going to look at view model, repository, and data source. So first we're going to see some view model patterns. We're going to compare two view models side by side. One is going to subscribe to a LiveData or consume data from a LiveData, and the other one is going to consume from a Flow. So the first pattern is about emitting multiple values. If we're not doing any transformations to the values, then we can just assign a LiveData that we're observing to the LiveData that we're exposing. That's very easy. In the case of Flow, we need to convert from a Flow to LiveData somehow. So we could use the LiveData coroutines builder. So we collect each item from the Flow. That's how you consume a Flow, and then we admit to the LiveData coroutines builder every item. Now, this is more or less readable, but if you have to do this 15 times in your model it's going to be a lot of boilerplate. So we created this handy asLiveData extension function that is going to convert any Flow to LiveData. And we're going to use it in the rest of the examples. The next pattern is emitting one initial value, and then emitting multiple ones. So we already saw this pattern. We used emit and emit source from the LiveData coroutines builder. In Flow we could use the same pattern. We pass the Flow converted to LiveData with asLiveData. But this is not very readable, and well, it's super awkward, and we're creating a LiveData twice. So it's much better if we embrace Flow's API, and we use this thing called on start that is going to let us define what the initial value of that Flow is. And then in the end we just convert it to a LiveData. And this is where Flow really shines. Normally you want to do a transformation on each item that you are observing or that you are receiving. And if we do this with LiveData, then you might want to try map first. But this would be main thread, and it would be awkward to use a coroutines from here. So it's much better that we use switch map, and we return a LiveData created with a builder. And because we are in a coroutines context, we can call this heavy transformation function, which would be a suspend function, and emit its result. With Flow this is much, much better. We just call map on the flow. And because we are not coroutines context, we can call the suspend function. And the end, we just convert to LiveData. YIGIT BOYAR: Right, so how do you use the suspend functions and Flow library in our repositories? Now, a repository is usually where you have a lot of custom call. Like you're making APIs, collecting data, filtering, whatever business as you have. In these cases, you may want to do out of transformations. And as Jose mentioned, we never intended LiveData to do these things. Like [INAUDIBLE] with Flow, you have all these operators on your streams that you can use. So just go ahead and use Flow or suspend functions in repositories. Similarly for your data sources, suspend functions on Flow are great fit, but here things get a little bit more trickier because this is usually the part of your code where you're talking to other libraries, remote sources, whatnot. So you don't control all of it. So let's look at all those cases, see how we can integrate them. The first case, the best case, is you have one shot operation-- so you have one shot operation to get some value like, say, from a network request. So if you're using retrofit, just mark the function as suspend and you're done. Retrofit already supports suspend function since version 2.6. Similarly, if you're using Room for your database, since version 2.1 it supports suspend functions. You tell it to turn suspend function, done. You don't need to care about anymore. But if you're dealing with a library that's not updated for the coroutines world, maybe it's a general library and it has callbacks-- it's not super old callbacks-- now you need to do some work. For those cases, we have this thing called suspend [INAUDIBLE] coroutine builder. This thing is basically the adapter between the coroutines world and the callback based world. What you do is when you call this function, it gives you a continuation that you're responsible to call. So we make our API request. If the API gives us a value, we resume the continuation. Or if the API tells us an error happened, we resume the continuation with an exception. This all you have to do. Once you do this, resolve the coroutine mission it works, and you can abstract this somewhere outside of your code. Notice that if you happen to call this continuation much later, like after it's cancelled, it's just ignored. It can also change just a little bit better so that if the coroutine's canceled, you can eagerly cancel your API request. Now, the third case is where have a lot of values, like you want to emit multiple values. And Flow builder is a great thing. It's very similar to the LiveData Builder except you build a Flow and you can emit values. So we're an example here that like infinitely every two seconds dispenses a new weather value. It's also super handy when you're writing tests, actually. Just like, say, oh, this is this other Flow. But if you are working with an old library again that doesn't support Flow and is a callback, you can use this call back Flow builder. This is very similar to the previous one we saw. So they say we have an imaginary API here that receives an image and a callback, and that callback has three functions. It's like next value, on error, and on completed. We register that callback with our imaginary API, and every time we receive one of those callbacks we just say, OK, offer that value. If an email came, offer it to the Flow, or it never happened. Basically close the Flow with this error. Or if it closes nicely, same way close the Flow. Now, we need to be careful here because this callback Flow builder, once it returns, flow is closed so you cannot dispatch values to it any more. We don't want it to return. So for those cases, we have this function called a wait clause, which basically suspends until the Flow is not collected data more. There might be any reason you close it here, or maybe the collector just stop collecting. And in that case, we simply unregister from our API. Now, testing. So we would like to talk about testing, but we don't have that much time. [LAUGHTER] No, no, no, no, no. No, we have time. Testing is important. So we have a full fledged talk on testing by Sean and [? Manny ?] tomorrow at 5:15. Please go watch the thing to see how you can effectively use coroutines in your tests. Before we leave, there is one more thing. With the introduction of Flow and the live cycle skills we have shown, you can actually get the benefits of LiveData without using LiveData. So you could be doing this where you say life cycle scope launch when started, and start collecting on this flow. This will behave exactly the same way LiveData behaves for your UI. Now, it's not 100% the same thing because LiveData has this caching behavior, but it's close to there. So is this something you may want to consider. For any questions, we'll be upstairs for the Q&A. Thank you.
Info
Channel: Android Developers
Views: 89,646
Rating: 4.9253464 out of 5
Keywords: type: Conference Talk (Full production);, pr_pr: Android, purpose: Educate
Id: B8ppnjGPAGE
Channel Id: undefined
Length: 18min 44sec (1124 seconds)
Published: Wed Oct 23 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.