WorkManager - Android Basics 2023

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys um welcome back to a new video in a new episode of Android basics in this video you will learn about work manager in the last video you learned about foreground Services which are used to just have something running in the background which the user is actively aware of such as when you track run of the user or when you have some music playback so just some kind of service or functionality that is active all the time in the background and word manager on the other hand compares to that at least because with work manager Google offers us something that helps us to execute tasks and functionality reliably and these are tasks the user does not directly need to be aware of um you can show a notification when the task is running but you don't have to in comparison to a foreground service so this could be things like if your app needs to synchronize data in the background so that could be something that runs periodically so for example every 30 minutes or just a one-time task and sometimes your app just needs to do these longer running things for example imagine your app needs to upload 20 images to a server then that can obviously take a little moment especially if your user does not have a strong internet connection and what would now happen is if you put the code to upload these 20 images directly in your app if the user then closes the app because they might not be patient enough the upload will be canceled and that is where work manager comes into play because it guarantees that your task will be executed and I could just show you the basics here of work manager but I don't think you will learn it really well with that so I want to also have a real live use case which I will show you here that will include some new things like interacting with the file system which I will of course explain but I think that way it gets the clearest in particular what we will build here is right now it's just a blank app but it actually does something if we go back to Google Chrome then this Behavior I will now show you is something you already know if you watch the previous Android basic videos um so especially if you watch the video about intense and intent filters because that is something I won't cover here in this video I will assume you know this if you don't then please watch the video about intent filters first but what we want to do here is we'll be able to Long click on an image in our Google Chrome browser which is a little bit light here and then we click share image you want to get that image in our app we select the word manager app here and then you can see we have an uncompressed photo and a compressed folder you can see the quality is I'm quite off here because it's compressed it's just a much smaller size you also notice that took a little moment a few milliseconds in this example but this compression is effectively handled in such a worker or work manager and why is just compressing an image a potentially long running task because in this example I will show you here we want to give a specific file size we want to compress this image to and it's not really predictable when choosing and choosing a quality for that image run to compress it to how large the image will be after compressing so we can't really predict by how much we need to compress it if we want it to be let's say one megabyte large so instead what we do is we can press it just a little bit check the file size if it's still too large we compress it a little bit again check the file size and so on so we could need to compress it quite a lot of times to fit into to finally find this right quality and if you not only do that with one image but potentially multiple ones which you need to upload to a server for example and this canonically take a little moment and we will actually do that in a worker that is how we call the things that a work manager controls and runs and jumping into an empty Android Studio project the only thing you need to do to be able to follow from the beginning here is to add these two dependencies in your build.gradle module app file so in this dependencies block since work manager requires to have this work runtime dependency and we also want to have the coil compose dependency which I've used in an earlier video of this Android Basics playlist which is just used to easily show images make sure to synchronize the product and then we are good to get started the first thing is we want to be able to send the image from chrome or from any other app that can share an image to our app and for that we go to our manifest that is something I taught in the video about intent filters and here below this intent filter we just declare another one the action what in this case be sent since we want to send an image to our app then the category would be a default and then last but not least we have our data specify the mime type which is just image slash asterisk so we're just looking for any type of image and I would say we just get started implementing our worker and that is also nothing other than just a normal kotlin class so here in our root package let's create a new kotlin class called photo compression worker for example select class and now there are different types of workers but we are interested in the so-called coroutine worker so this is just a worker that is running in a suspend function so it's asynchronous in the end and you can also see we get a bunch of Errors because we need to add some default Constructor arguments here on the one hand that is the application context and on the other hand that is a set of parameters of tab worker parameters so if we want to attach some data to these workers so I'm just pass some some kind of parameters to it we need to do this with these worker parameters and we can then retrieve it with this variable here because we won't instantiate this class nor own and that is what the Android system will do and that is why it also needs this extra parameter here since we can't just pass as many values here on our own as we want there are ways to also inject or to also pass more variables here to the Constructor but I won't get into that in this video we then need to take these two variables pass them here to the Constructor of coroutine worker then this error is gone but we had another one because we need to override these so-called dual work function and that was where the magic happens this is the function that will be executed when we want to run our worker so we could potentially just run once when we for example want to upload a set of photos but if we for example want to synchronize our data frequently maybe every 30 minutes as I said then we can also run this periodically so then this do work function would be executed every 30 minutes reliably and we can then also later specify so-called constraints such as conditions under which this worker will run so such a condition could be uh the end of device needs to be connected to the internet if you're dealing with a data synchronization then that obviously needs to be the case and Android or work monitor will then already take care of that that this do work function will only run if your device is connected to the internet and if we take a look it returns a result so we now need to tell this worker in which scenario this work is considered a success in which it's considered a fail earlier and which scenario we want to retry the work at a later point and let's actually get started implementing this function so what do we want to now send to this worker well we want to send an image here but it's not so easy to actually send a whole image in form of byte to this worker because um there's a limit in byte count that we can send to this which is I think 10 kilobytes so um it's not enough for an image so we instead want to send the Yuri so the actual address of that image which we can then use to retrieve the actual bytes of that image inside of this worker so Val string theory is equal to our parameters Dot input data that is the set of data which we can pass from the outside and here we want to get the string now we need to specify a key so under which key or how we identify the specific piece of data the Ziri and for that I would like to have a companion object or we have a constable key content Yuri and that is just a string key content Yuri we also want to specify two more of such keys since we have two more pieces of data we need to take care of here on the one hand and that is the key compression threshold which will specify the final file size in bytes up to which we want to compress this so if this is for example 10 kilobytes then we will compare the image until it reaches a maximum size of 10 kilobytes and for the third key we actually want to have a key result path which will be the file path of the final compressed photo which will then pass back so we can then read it in our activity and show it in our composable so here for the string theory we then want to pass our key content Theory and we also want to have our compression threshold in bytes this would be just a long value so param's input data get long and here we pass our key compression threshold and the default value is just zero L we then want to take our string theory and actually make a real Yuri of that so um that is an extra data type of Android but we can't pass that here in such an input data map so we need to say we have our Yuri and that is equal to yiri from androidnet.parse and here we can then pass or string theory to construct the real URI out of that and now having this really read we want to take that and read the actual bytes of the image here into memory so we say valve bytes is equal to our app context.content resolver so the content resolver is used to well resolve some content such as such an image from our Yuri and maybe I will actually make a separate video about yuris itself because you need these quite often in Android and the different types of theories but if you have a so-called content in giri which the theory would be if we share it from another app then we need the content resolver to read that theory in as a byte array and specifically we're going to open an input stream here so a stream that will read this Theory we pass it here and then we sell quick then we say question mark.use and we now get an input stream we can then use to actually read the bytes that is super simple we just want to say it that read bytes that returns a byte array which will be saved in this bias variable use what that will do is basically it will just make sure that this input stream is automatically closed after we use it so we don't need to take care of that on our own but what now happens if the zeroe doesn't exist so if we pass some kind of invalid area then this would return null and we wouldn't have a byte array here because if we take a look it's another by the way and with a nullable battery we can't work so if it's now we want to return here and now we need to return such a result we can construct that very easily by just saying result Dot and we have a failure retry or success since in this case if the Yuri is invalid it obviously failed we want to return result.failor and we might also want to consider switching the carotene context here to the iotis picture this Patcher since we're dealing with I O operations such as reading an input stream um so what we want to do is when we turn with context now that is new to you then please watch my separate core routines playlist and that will be too much to explain that in detail here what uh this with context block does but it pretty much just makes sure that this code runs on the um on an optimized thread for my operations so right here we return with context then we also need to modify this return to return at with context and then down here we can go on because in right now take a look again this is now a not notable by the array anymore so now that we have our byte array how do we actually interpret that as an image and compress that image for that we need to convert it to a so-called bitmap that is just an image object that we use in Android so Val bitmap and we can also easily construct that with a byte array by just saying bitmap Factory decode byte array here we need to pass that by the way we want to decode which is our bytes the offset is zero because we just want to start from the very beginning of that byte array and the length is simply byte.size and now bitmaps also offer this bitmap.compress function which allow us to specify this quality here so if that quality is 100 now that means it's 100 quality so the highest quality pretty much lossless quality if we set this to 50 then it will be compressed by 50 and so on but again as I said this is not predictable in which file size it will result after compressing so you can't just say if the quality was 100 and it's now 50 that the file size will also just um be divided by two it's sadly not that simple so instead we just need to compress it a lot of times until we finally hit the right file size and for that I want to have an output stream this time since we now want to take the compressed bytes and write these out in this stream that is simply a byte array output stream we can also just specify the type here by the way like this because we will initialize it um right after and we want to have a quality uh variable here which starts at 100 because if the image already fits in our desired file size then we don't need to compress it so we just start at 100 lossless quality and then we will use a do while loop so we first want to do something before we check the while condition and in here we actually want to Define our output oh actually I wanted to define the output bytes up here not the stream itself so these need to be bytes and the byte array and in here we will have the Stream and that is now abide array output stream because with every iteration of this while loop we will create here we will create a new output stream read in the new compressed bytes and then check how large that array is which would be the byte size so we can then take this output stream call that you use again here we get that output stream output stream I always say bitmap.compress the format would be bitmap compress um bitmap formats or actually compress format dot jpeg the quality is just what we have for quality and our stream we want to compress that into is our output stream then after that compression is done we say our output bytes is now equal to our output stream that two byte array so that is now the compressed bytes after this compression happened so the little bit smaller image you can say and that would say our quality minus equals quality times 0.1 so we just um subtract 10 percent of the current quality and we say round to end and then we check again with that new quality if after that compression it finally fits in our file size if not we will compress it again with a little bit decreased quality again 10 percent less and we try again so after this do block we now have a while loop but we will check if our output bytes dot size so that is the actual byte count of that compressed photo as long as that is larger than our compression threshold it bytes and let's also have a check that the quality is larger than 5 because then it doesn't make sense to further compress this at some point we just need to stop if that condition is given then we just repeat this do block so then we again to find a new array output stream here we again compress it with the new quality which was decreased in the last iteration and now we again check okay is it now a good size if not we just do this whole thing again and so on and now since we want to return the file path so key result path here we need to just put that in a file and save it in our file system in our Android storage again for details about Android storage I have a whole playlist that is quite complex but in this specific use case here it's actually very simple so we just want to say valve file is a new file from java IO here we need to give it a parent and a child so the parent would just be the path where we save it the child would be the actual file name we can get the directory with app context.cache directory so we just want to have a cached file so we don't want to persistently save the nor file system at least forever and we now need a unique file name we could also choose a hardcoded volume here since we only say one file at a time but if we want to potentially save multiple images at the same time you of course need different filings um you could use the ID of this worker because every single work request or worker has a unique ID which you could use here from powerms.id and we could say that jpeg for example then we go down here we say file that right bytes so we just tag our output bytes and write this into our own file so here we now have that file finally saved on our file system so we now can return a successful result here since we're not done so just return actually we can just say result.success and here we can either just return success or we can also provide an output data so if we have some data to pass from this worker to whoever calls this worker then we can pass that data here and this with this output data parameter and we actually do have output data because we want to pass the file path from this file to our activity later on so we say work data off that is how we construct that and here we can just put in lots of pairs on the one hand the key on the left side so key result path and then we say two and then followed by the actual path in this case so file that absolute path and yes that is an example of a work manager worker and in here you can do pretty much everything you want which is a long running action and the Android system will then guarantee that it is called you also have the option to call um something with foreground um set foreground so if you also want to show a notification while this worker is running you can also do that so that would then really compared to a fragrant service just that this is kind of expected to finish at some point while foreground service is rather something that could be active all the time and that does not have a specific finish time or rather it is up to the user when the flow run service finishes so when the user stops listening to music when the user finishes their run so the notification will hide or actually be completely removed and not shown anymore but on the other hand if you execute things in a worker then that is just something the app needs to do which isn't connected directly to a user action so I hope that somehow gets clearer but let's now take a look at how we can use this compression worker and execute this work request and for that one to go back to our activity and as I showed in the video about intense and intense filters we want to override on new intent here and this function only makes sense to be override if we go to our manifest and declare this activity as single top so launch mode single top since then the all-new intent function will be triggered and in here we then get the intent which contains the Yuri of the photo we want to compress so that would be the photo that comes potentially from Google Chrome so let's first of all retrieve that theory with the method I showed you before so I'll first check on which version this Android device is running build.version SDK int if that's large or equal then build that version codes that tiramisu then we want to say intent that get personal extra and we say intend that extra stream here we also want to specify we actually want to have a Yuri I'll enter to import that you redouble calling class of java and if we're running on a lower version we also say intent get parcelable extra but here we just say intent extra stream without specifying the class if there is no Yuri so if that's null we can just return Here and Now what we need to do to launch this worker is we need to define a work request with that we can Define some configuration so basically conditions that need to be true to execute this work requests we can pass our initial data we can set a time when it should run we can set a delay so these kinds of text so when I have a request here and that can either be a one-time work request or a periodic work request so periodic as the name says would run frequently so every X minutes every X hours every X days if you for example want to synchronize your data every hour then this is the work request to pick however if you just want to execute this once or every time the user does something like upload some photos then you want to have a one-time work request so when I say one-time work request Builder here we need to define the type of worker we want to launch which is a photo compression worker and that we can now configure that on the one hand we want to set input data so set input data that is now the same thing we did as the output data so we have a work data off and here we specify the pairs which we retrieve in our worker with um with these functions here so get string and get long so we need to provide the key condition on the one hand and the key compression threshold so back here we'll have our photo compression worker key content Theory and that is our urine that we have read from the intent so you read that to a string and we also have our key compression threshold which is something we can specify here so for example one kilobyte times 20 that would mean we want to compress the image that we pass to 20 kilobytes done something else we can do is we could set constraints as I mentioned so here we can initialize constraints and then decide okay does our worker require internet so then work manager will take care of that and not execute it if there is no internet since we're just saving files that is not required um requires charging is something we could set so that the device is connected to a charger that it's idle so that the user is not doing something that the battery is not low that the storage is not low that is something we might want to consider here since we're saving files so all of those are just edge cases that can always happen but the work manager makes it very easy since it handles this for you and there are just a lot more things we could Define here um so we can also Define if we have multiple workers we can we can chain these that the first worker gets executed then the second one pass some data to the next one so there is really complex step we can do with work manager but usually you're just dealing with this more simple stuff but it will also for example Define a bag of criteria so um how should work manager handle when you want to retry the result should it be a linear policy so that it retries for example every minute or should it be an exponential policy that at the time until the next retry just grows exponentially so there are a lot of things you can configure here you can set an initial delay so that it waits for example 10 minutes until it executes the work request you can schedule it so lots of cool things to play around with we just want to call build here to finally build our work request which we haven't fired off yet to fire it off we need to get a reference to work manager itself which we can just have a public or verbal for actually Global World another public one a private VAR or latent advar work manager is of type word manager we then initialize this here is equal to word manager that get instance and we pass our application context than going down here we then say word manager a DOT and queue so we have NQ here um we have NQ unique work and NQ unique periodic work um so NQ would just uh yeah enqueue this single work request which we have here which is the one we want to choose and cure unique work would make sure that there's only one instance of your work running so there could only be one compression at a time which doesn't make sense in this case and now within queue workmanager will just take care of executing that work request at the best possible time so if the storage is maybe very low for the user then workmanship won't execute that right away but rather wait until the user has some storage again but how do we now get this result of the worker which we pass here this absolute file path inside of our activity and really display that in an image for that we just need a little help of a v-model which we want to create here to just have some stage which we can update there so thought of your model for example make that inherit from viewmodel he went on the one hand have an uncompressed Yuri which is um a nullable Yuri Alt Enter to import that by mutable state of null initially so that's just the Yuri of the uncompressed photo we can Alt Enter to import that make it a private set and then also have a VAR for our compressed bitmap now notable bitmap by mute will stay off not initially so there's the compressed photo which we can update here um to trigger a change in our UI and finally we want to have a bar work ID which is a uuid nullable bimutable state of null since when this work ID changes we need to also change which workers data or which workers result we observe and where we listen for changes in our UI so also have a private set here and actually here as well and then just provide some functions to update these values function update and compress Theory with a nullable Yuri always say uncompressed theory is Yuri we then do the same for the other fields function update compressed bitmap with a nullable bitmap compressing bitmap is bitmap and last but not least update work ID with a uuid and we say work ID is give you ID oops like this you can then go back to our activity and instant in such a view model up here private Val viewmodel by viewmodels this one here specify the type of your model just photo viewmodel and then we can now update the values or remodel here in our all new intent function that will then trigger a change in these states and that however will then trigger a change in our UI block here that is just how we communicate from this on new intent function with our actual compose code so here in all new intent after we retrieve the Yuri we can call viewmodel update uncompressed Theory which is just the theory we get from the other app from Google Chrome for example just pass the Aries here and after we've enqueued the request actually credit the request we can say viewmodel Dart update work ID with request dot ID so that will then allow us to listen for updates of that specific worker we've launched here with this request so if we now scroll up to our actual UI we can listen to the worker result here by saying is equal to viewmodel.work ID if that is not null we get the ID here and then we say word manager a DOT get work info by ID actually um by ID in live data live data is something I haven't covered here and I also won't cover in this Android basic status but it's basically an observable data type so something that triggers Whenever there is a certain change in this case whenever there is a change in this work info so for example if the result is ready and that is just lifecycle aware so it makes sure that there are no leaks inside of an activity so here we can then pass our ID which we got from this work request before and we can then convert that to compose state by saying observe and I'm noticing there's actually the penalty missing to have this function which converts this live data object to a compose state so we actually always get the updating compose whenever the worker is finished so let's just go to our build.grader app file scroll down and also paste this dependency you can also find this in GitHub below but also just write it off probably very quick synchronize now go back here and then have observe as State this green function here is the one we want press enter and now we have a worker result which is a state of work info you can also directly refer to its value so we just have the work info we are looking for and that however again now also contains the result of our worker which we can then react to in our compose code so now what we can do is we can have a launch defect block with our worker result dot output data so whenever our output data changes this piece of code is executed and we now want to check if worker result.output data is not equal to null so if there is some output data we want to do something specifically we want to retrieve the file path of the file of the compressed Yuri of the compressed photo and we get that with worker result that output data um we don't need the nautic here but here actually not um get string and the key is photo compression worker key result path so here we now retrieve the file path that we passed to this uh successful result here after our worker was running and if that file path now exists we know that the compression is definitely finished so if it's not null we can reconstruct the bitmap out of that file path so we can read that file as a bitmap by saying bitmap Factory and decode file and we just pass our file pass file path here we then say we model update compressed bitmap and we pass this bitmap that will then trigger a change of our compressed bitmap state which we can then again listen to in our compose code which will come now to just display these two images let's have a column modifier modifier film exercise we want to Center that Center vertically and horizontally add in here if overview model Dart um uncompressed Yuri if that exists so if we shared a Yuri then we want to have a text for the uncompressed photo followed by the actual image so we use async image from coil and here we display the content query we don't use a Content description here and we can then also have a little bit of spacing here so the spacer with height of 16 DP for example Alt Enter to import TP copy this piece of code paste it here this I want to listen to the compressed bitmap so this time we get a bitmap for that to display that we don't even need coil so we can just display a normal image here that takes in this bitmap and the bitmap is just it that as image bitmap which is just a composed version of bitmaps we pass now for the content description and that should be it I would say we launched this and try this out this is obviously still in the old app but let's take a look if we can compress a photo from Google Chrome now so right now nothing is displaying if we switch back to Google Chrome actually that is not Google Chrome let's close this and scroll down maybe take this photo here a long click on that Click Share image and when I click on more and then we want to select our work manager app that appears here click that and nothing is working okay well then I need to take a look at what that is and then you will hear back from me okay it seems my code was working well but that this constraint was actually meant that my storage was low on this emulator so maybe it just worked for you right away I'm not sure what the exact bite size is that the workmanship will look for so if the device has a Less storage than the specific size that it won't execute the worker but I removed this constraint here so don't have it anymore and now as you can see it's working fine if we go back to Chrome now I'll take this image for example and I long click on that no go back yes share image share with work manager and then you can see it will update the image in the app and again let's try it one more time with this happy birthday share image work manager there we go so now it's working every single time obviously the constraints are working as you can see but that is of course something you need to decide for your individual use case if such a constraint is really necessary um for this use case I don't know um you would need to look into the exact file size um that shouldn't fit anymore on the device's storage but even without this constraint you can see we can save a lot more images here on our device compression also seems to work you can see it's a little bit blurry and the pixels aren't as crisp as here so that is looking quite nice it's a little bit longer video of this Android basic playlist but I think work manager is important I also have another comparable video about that we also show these um periodic work requests but I think this video here again just um fits more into this Android Basics playlist um to really co-work manager from the beginning to end with a real world use case so thanks for watching if you enjoyed this definitely leave a subscribe if you haven't already since then you will get two more Android videos every single week about topics like this and I wish you an amazing rest of your week see you back in the next video bye bye [Music] foreign
Info
Channel: Philipp Lackner
Views: 50,961
Rating: undefined out of 5
Keywords:
Id: A2JetouoNSc
Channel Id: undefined
Length: 34min 22sec (2062 seconds)
Published: Sun Jul 09 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.