StateFlow & SharedFlow - The Ultimate Guide to Kotlin Flows (Part 3)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome to part three of the ultimate flows crash course in which i will talk about state flow and shared flow that's actually a question i get super often how they compare also how they compare to maybe live data i already have a video about that but i will still explain a little bit about that in this video and just show you how you can use them when you should use them in practice all that cool stuff so let's actually jump right into android studio in our project here just where we left in the last video where i charge flow operators and in this video as i said we will deal with shared flow and state flow let's start with the latter with state flow what is that actually used for so state flow as the name already says is used to keep state it's just like live data without the life cycle awareness so our activity can't really detect when or rather the state flow can't really detect when the activity goes in the background so other than that it's actually the same as live data so it will notify all of its observers or rather collectors when we're dealing with flows if there is a new change and it will exactly keep one single value so it's not like this flow that count and flow that we implemented here that we can emit multiple values over a period of time instead it just holds a single value and that behavior also makes it a so-called heart flow so this flow this count on flow is a cold flow i explained that in that corresponding video a cold flow just means okay if there are no collectors then it won't do anything however a hot flow which state flow is is it will also do something if there are no collectors so if we assign a new value to state flow then we change that value even if there are no collectors but let's actually see how this works so typically we have all of our state flows in our view model and these just contain our ui state we declare these typically within with a mutable version that is private that only the view model can modify and then we will have a public immutable version that our ui actually can subscribe to and that our ui can collect so let's call that underscore stateflow so the underscore version always means it's mutable so we can simply say mutable state flow and we actually need to provide a starting value because as i said stateflow keeps a value so we need to provide a value it actually starts with let's just also store a number in that just for demonstration and start with a zero now to actually create the immutable version of that that we can't change that we can only collect we can say val state blow is equal to underscore state flow as state flow so this as stateflow function will return an immutable version of that state flow and then our main activity can actually collect the changes of that state flow so let's say we have a very simple increment counter here and we would like to also reflect that counter in our ui after we rotated our device because as you probably know as android developers when we rotate our device then the activity is recreated which means all the ui state is lost and if we don't store that ui state in our view model then it will be lost so that's why we have these state flows to just store our ui state in it just like that counter here so it starts at zero and let's now write a simple function that will increase it increment counter which we can then call from our ui and here we just want to increase that value of our state flow by one so we can say underscore state flow that value is equal to state flow that value plus one or you could we could simply say plus equals one so this looks very familiar from live data there we also had this dot value that we could change and that's exactly the single value our state flow now holds so now if we call this function then the value in that state flow will obviously be one and if we call it again it will be two and so on and as soon as we rotate our device then the collectors or that stateflow will actually fire off again and notify our ui about that current value which would be one if we call that function exactly once so let's actually jump into our main activity or ui and yeah i don't know let's remove that text for now and replace it with a simple button that we can click on when we click it we simply want to call our review model we do have a reference yes viewmodel.incrementcounter and in here we can say we have a text that says um yeah let's say counter and we simply want to pass the value of that counter so we can simply do the same as here instead of time we can say let's say count we want to do that with our state flow and that's now the immutable version so here we can say view model oops we model state flow value is equal to something you can see that gives us an error because this is immutable and that the the ui should never change the values of the state flows directly from the view model so there we go we just observed that as a state here the initial value should be zero and then we can simply show that here with count.value especially if we deal with compose this is pretty easy when this is already everything you need to do as i also said in the previous video i don't recommend using stateflow if you use compose because compose already comes with a state so then you don't need this collected state function but sometimes you get state flow maybe from a library or so and then you can use this collector state function but i will also show you how you can actually subscribe to changes of a state flow in xml so if you don't use compose then this is actually more interesting because you need to consider quite some stuff there but let's first actually launch our app here and see how that works okay there we go the app is launched and the button is a little bit misplaced i don't center it but that really doesn't matter here however if we click that counter now then we are actually able to increase it that's probably not a surprise for you here but let's see what happens if we rotate the device then nothing happens i don't know if rotations are actually disabled yes looks like that let's try again and yeah you can see the the value is actually kept however that is composed state and that's nothing special if we store the state in our viewmodel let's see how that actually would work with xml how we can actually observe and collect the state flow in an xml project i will just do this in the same project here just outside of this set content block but yeah if you don't have compose it will work exactly the same so on the one hand a lot of people actually think they can just do lifecycle scope.launch and then say okay we need a viewmodel reference here private valve view model main view model by view models that's how we get a reference to that view model in non-compose and most people think they can just do your model state flow collect latest here maybe so for state state flow i would always use collect latest because it reflects ui state and you really you're only interested in the latest change of that ui state and here you would simply get that number and you could then have your text view whatever that is called you could say binding tv counter that text is equal to number two string or so of course we don't have that text view here it doesn't matter but that is how you would actually do that in xml however as i said most people think this is enough to just use lifecycle scope because you know lifecyclescopes has that is lifecycle aware and that it will automatically cancel this flow when the activity goes in the background but that's actually not the case so here you really need to take care this is not enough instead what we need is we need another block here and that block will be repeat on cycle in the past google actually said it's enough to say launch when started however it seems like that's not the case anymore because they removed that from their documentation and now it's actually you actually need to use the repeat on lifecycle function and here you can basically define a state when the block when the code inside of this function basically the code in here will be cancelled on the one hand and relaunched on the other hand so we can say lifecycle state started and that is now safe so that's not the same behavior as you would get with live data because i know this is quite some code to actually just collect a flow estate flow in your ui and you need to do that very often for normal project because you are not going to have only a single number as state i actually usually create a very convenient function for that so we can we don't need to type all this every time by hand and we also don't have like three indentations here instead just one so let's just do that down here that's a cool function i will call this um it's an extension function which also has a generic parameter i will explain what all that means when i wrote the syntax and it will actually extend our activity or your fragment if you have that the component activity in this case in an xml project it would be app combat activity but here we need component activity and i'll call it collect um latest lifecycle glow you can also create a version of that to just have a normal collect function and not collect latest so on the one hand we want to have a flow here we want to collect and then we actually have a suspend function that gives us our generic parameter t so lambda function and actually we also need to give that a name like collect then in here we can just type our stuff by hand so we first of all want to launch our lifecycle scope so cool routine in that then we have a repeat on lifecycle block we can say lifecycle state started and here we can just say we collect our flow actually we want to say low dot collect collect latest here and then we pass our collect lambda function here so that's just now a convenience function to save all these indentations here we can then scroll up and instead of all this stuff we can simply copy whatever we want to change when we collect animation and we can just say collect latest lifecycle flow we want to collect our view model state flow and here we then get our number in and we can assign the value to our taxi and that's now safe it's just a single indentation as if you would normally collect the flow so you don't even need to launch a curtain anymore in lifecycle scope because all that stuff is handled here inside of this function you can see we launch a curtain and yeah that's pretty safe now and that's pretty much already everything for state flow i really prefer that when it comes to state flow versus live data because you just get all these flow operators so with this state flow you can the same way just use map you can use flat map all the stuff we also used on our other flow and with live data you don't have that many cool operators and it just uses cool routines which is much cooler than live data and the other part of this video as i said will be about shared flow so what is shared flow actually how does it compare to state flow how does it compare to the normal flow well shuffle first of all is used to send one-time events so when it comes to these emissions then there are basically two types of emissions on the one hand we have that state emission that we had with state flow which should actually persist so when we rotate our screen then the state flow will fire off again and notify all of its collectors so after we rotated it they rotated the device this block will simply fire off again and assign the current value of the state flow to our text view however there are also events that we would actually only like to receive a single time like let's say you want to show a snack poor or let's say you want to show you want to navigate to a different screen and you want to send that navigation event from your viewer model to your ui let's say you log in and the logic for that is in your viewmodel and only if the login was successful your viewmodel somehow needs to tell your fragment that it should actually now navigate to the next screen and that navigation event should only happen once it shouldn't actually be fired off again after we rotated the device because that would lead to a crash most likely and for these things we use ship flow so in case you're familiar with channels shared flow works very similar like channels channels are i think meant to be used with a single observer so if there's just one collector you can use a channel to send events into and if there are multiple collectors listening to changes then you can use shareflow but i pretty much always use shareflow for everything you're totally fine with that but what i'm saying is shareflow is just set up to also work with a lot of collectors and the same as state flow share flow is also hard flow so if we send an event into it and there are no subscribers no collectors then the event will be lost so it won't somehow be kept in the flow or yeah the flow actually will do something if there are no collectors that's what i'm trying to say here the cool thing is let's switch to our review model and create such a shared flow we can actually copy the state flow because we handle this the the same way as with date flow we can call this underscore shift flow and rename all this and this doesn't need an initial value because we just send events into that we can just say mutable flow we need to specify the type of events we send into that and then we say as flow so now the cool thing is that you can see we don't really have that flow builder like we did here so with normal flows we need to define how the flow looks like we already need to define which emissions are actually fired up in which in what kind of sequence with shared flow it's it works differently so now we have that shared flow object here and we can use that from any place now in our viewmodel to send an event into this at any time so let's actually write a very very simple function to do that let's say we just want to send a number into that shared flow and then it will simply square that and emit that's created number so let's say function square number we pass the number we wanna like we would like to square and then we can simply do shareflow that emit so that's also just like above here we also have an emit function but now with the share flow we can call that from anywhere in our code like anywhere in our view model and then we can simply say okay we want to emit number times number you can see we get an error here because emit is actually suspend function it will suspend as long as all of the shared flows collectors need to process that event so let's launch a curtain here and try to understand what that means proteinscope.launch view modelscope.launch actually we send that event into this now when there are collectors so when there are maybe here viewmodelscope.launch and we say shut flow that collect here we would usually like to use collect and not collect latest because we don't want to drop events that typically shouldn't happen um because that's not a thing where only only the last event matters no if we send a snack bar event and a navigation event right afterwards then we would actually like to receive both events and not just the navigation event with a state that works differently so here we have a collector and let's say that takes two seconds to process that number you can imagine that's a network call or whatever and then we can simply say print line low um the received number is it and then we could also launch another curtin scope here to also have another collector make sure to actually launch another curtain here because this actually never um never finishes here so this collect block suspends the current and it will actually never reach this point of the code so you need to launch a new creatine if you have want to have another collector and here let's say that takes three seconds to process the number let's also say second flow and first flow so this emit function now suspends the square routine as long as all of the collectors of that shareflow need to process it so since the first collector needs two seconds to process it and the second one needs three seconds it will actually also suspend this croutine for exactly three seconds because that's how this collector actually needs so let's actually say instead of collect flow here we just say okay we're on a square number let's send three into that and launch our app if we then take a look in logcat and search for flow oh of course i need to remove this binding here let's launch that again take a look in logcat wait for gradle to finish building there we go it's installing and let's see if something actually happens and you will realize nothing happens so why is that i mean we do send the event here into this shelf flow and we do have two collectors but they're not triggered what's the reason well as i said shelf flow is a hard flow when we send an event into it and there are no collectors the event is simply lost at this point where we send the event there are no collectors because we we call these collect functions after we send the event so just to show you how that actually works if we have that square number event after we actually declare these collectors launch that again then we can see first flow received the number and now the second flow received the number as well so that way it actually works so yeah take note that this is actually really a one-time event now these collectors are fired once because we send event send an event into that once but now they're actually not triggered anymore because we we just send one event here with that square number function however in some scenarios you actually would like to have that behavior that the shellflow kind of caches some emissions so that if we send something into the shell flow that it keeps that for potential new collectors and there is actually a cool way to do that in the constructor of the immutable shed flow you can see we have this replay parameter and the replay corresponds to the so-called replay cache and we can specify a size here so we can say replay is let's say five what that would mean is it would actually cache five emissions in the flow and when there are new collectors then these will receive these emissions that are actually cached so if we now take the square number event again and put it before our collectors which initially didn't work then you will see our flows receive the events because they are essentially cached at least like there are five events cached at max in this flow here we send an event so it's actually kept and when new collectors subscribe to that flow they immediately receive those cached events so quick recap stateflow is used to keep state to to keep values that you also want to re-emit on screen rotations and shared flow is used for one-time events like you don't want to show a snack burn error snack bar again after screen rotation you don't want to have another navigation event or so you don't want to hide the keyboard again all these one-time events for that you should use shirtflow maybe very quickly if you want to collect that in jetpack compose that's a little bit different so you can't use collect estate because that will actually make it a state and not a one-time event because compose doesn't really know this concept of one-time events but we can still kind of simulate that by using launch effect simply passing through for the key so this is never actually re-launched this launch effect block so that basically just launches security in here in which we're actually safe to do some stuff that's that's happening outside of the actual composition and then in here we can say um viewmodel sharedflow collect and here we then get the number and this is now triggered whenever the numbers actually whenever that's actually an emission and here you can then do stuff for jetpack compose to actually show sniper or so and as i said if you want to do that with xml you can simply use this but i would use it without collect latest so then i would create another version of this like just collect lifecycle flow and remove the collect latest here and then you can use that for your shared flows so then no events will actually be lost in the initial video of this playlist i actually said there will be three videos in total but i decided to make a fourth one so the next video you will actually also learn something about testing flows because that's pretty important if you want to write unit tests for your apps and it's really not that easy to unit test flows and curtains so if you're interested in that then you will find the video right here when that is out you
Info
Channel: Philipp Lackner
Views: 1,930
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile
Id: za-EEkqJLCQ
Channel Id: undefined
Length: 23min 10sec (1390 seconds)
Published: Mon Dec 13 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.