[MUSIC PLAYING]
CAREN CHANG: Hello, and welcome to the first episode
of our WorkManager series.
WorkManager is now the recommended solution
for long-running tasks.
So in this episode of "MAD Skills,"
we'll take a closer look at how to get started with using
the WorkManager APIs.
By the end of the video, you'll know how to define, schedule,
and chain work requests.
We have a lot to cover, so let's get to work.
To help you follow along, we're going
to be using the WorkManager colab as a basis.
Our goal in this colab is let the user blur an image.
And we're going to be using the WorkManager
API so that the blurring work can be done in the background.
WorkManager is an Android Jetpack library
that lets you schedule long-running tasks reliably.
Previously, there are different job scheduling APIs.
Some of these APIs only worked on specific Android versions
or if Google Play services were installed.
WorkManager works with all versions of Android
since Ice Cream Sandwich regardless of whether Google
Play services are installed.
The other great thing about WorkManager
is that you can schedule tasks to run based
on certain constraints so that your task only runs
when the constraints are met.
You can also chain work requests so that jobs run sequentially
depending on whether previous jobs were successful.
All right, now that we know a little more about WorkManager,
let's start looking at some code.
In WorkManager, there are two main classes we care about--
the worker and the work request class.
The worker class is where we're going
to define the work we want to perform in the background.
We do so by creating our own worker class
and overriding the doWork method.
This method runs asynchronously on a background thread,
which means they won't block the user from interacting
with the app while our work is being done.
Here we created a BlurWorker that extends the worker class.
We then override the doWork method
to implement the work we actually want to schedule.
In this case, we're taking an image as an input
and blurring it with the createBlurredBitmap method.
doWork should eventually return a result
to let us know whether our work completed successfully.
Notice that we can also include an output as part
of the result. These result functions
are overloaded and can also accept a WorkManager data
object.
Data serves as structured key value storage
that you can pass around.
The easiest way to build data is by using
the workDataOf extension function.
In this function, you can then map one or multiple keys
to values and return them as part of the response.
There is a hard upper limit to how many bites
you can pass around with a data object.
And our bitmaps are definitely above that limit.
So instead of passing the bitmaps around,
we're actually passing the URIs of the bitmaps.
Here, we're using the URI of the newly blurred image
as our output.
By adding an output to our result,
we can then use this output as an input for future tasks
if we decide to chain multiple workers together.
When we chain tasks together, the output from the first task
will be available as an input to the next task.
And we'll see this in action a little later.
Having said all that, we should return success
if our work was completed successfully.
On the other hand, if the scheduled work was
unsuccessful, we should return a failure result. In some cases,
we might even want to return or retry
result, which means something went wrong,
but we should we retry this work at a later time.
Once we've defined the work we want to do,
we need to actually schedule it with WorkManager
by using a work request.
And we could take a look at how that's done in BlurViewModel.
The WorkManager service is responsible for scheduling
all the work that we request.
And it takes into account the system's resources
to ensure that the work is spread out evenly.
The work request class allows us to define how and when we
want our work to be executed.
For example, we can specify this work
to run periodically, only when the device is
connected to WiFi and power.
Here we've got a WorkManager instance,
and we use it to create a one-time work
request to execute the BlurWorker that we
created earlier.
We then enqueue this work request
so that our job can actually run.
Notice that we're using a one-time work request here.
WorkManager can also help us run tasks periodically.
For example, we might want to backup data once a week
or download new data every 24 hours.
To do this, we can use a periodic work request instead.
But since we just need to blur this image once,
one-time work request will get us the job done here.
OK, so far we've scheduled work to blur the image.
But this isn't very useful yet because we haven't actually
saved the new image.
If we remember, BlurWorker is currently
outputting the URI of the newly blurred image.
So now we have to take that URI and save it to a file.
Luckily for us, WorkManager has a pretty easy way
to chain multiple work requests together.
By chaining requests together, we
can first execute the work to blur the image.
And then once that work is done, we
can execute a second worker to save the blurred image
to the file.
Since we need some new work to be done,
let's create a second worker that
handles saving the file of the newly blurred image here
in SaveImageToFileWorker.
We didn't specifically need to create the second worker class,
but it's good practice to keep separate workers
for specific tasks to keep things clean.
You can see here in doWork is where
we implement saving the file.
Our input data here will come from the output
data of BlurWorker.
And that's how we'll get the URI of the blurred image
so that we can save it to a file.
Now, navigating back to BlurViewModel,
we need to create a chain of work requests.
Now that we have more than one worker,
we can use the then method to queue up the second work
request after the first one has completed.
Calling then allows us to run tasks sequentially.
So first we blur the image, and then we save the image.
This also means that the output data of the first work request
will automatically be the input data of the second work
request.
If instead we wanted to run tasks
in parallel instead of sequentially,
we could use the listOf method instead.
OK, great.
Now that we've implemented the two work requests
and chained them together, we'll actually
be able to save the image we blurred.
Our app can now blur and save an image.
But we do have one small problem.
If the user continuously taps on the Go button,
they'll end up triggering a chain of work events
multiple times, causing a lot of redundant work we don't want.
To ensure that the work we schedule is unique,
we can use the beginUniqueWork API instead of beginWith.
You'll notice that beginUniqueWork asks us
for a couple of extra arguments.
First, we need to give a name to our unique work
so that WorkManager knows how to detect duplicates.
Then, we need to tell WorkManager
what to do when duplicate work is detected.
Here we have a few options.
The first one is to replace the existing work in progress.
This means that if WorkManager detects duplicate
work being done, it will stop the work that was in progress
and start a new one that was requested instead.
Our second option is to keep the existing work.
This means that a new work request
will be ignored since there is already the same work being
done currently.
There's also the APPEND and APPEND_or_REPLACE strategy,
which append the new work after the current work is done.
Since we only need to blur the image once,
we're going to use the KEEP strategy here.
That way, our app will not start blurring another image
until its current work is done.
For a last step, it would also be
nice to know when all this work we scheduled
has finished running.
We could do that by getting a live data that
holds a list of work infos.
Work info lets us know the status of a work request,
whether it's blocked, canceled, enqueued, failed, running,
or succeeded.
These different states can be helpful in letting
us know if our work was completed successfully,
or if we need to retry it.
And get these work infos in three different ways--
using the unique ID of a work request,
using the work request's unique chaining,
or using the tag of a name of a work request that we add.
For our app, we're going to add a tag to our work request
so that we could query for the work info.
Now outputWorkInfoItems is getting live updates
about the status of our work requests
for the TAG_OUTPUT tag.
Our next goal is to show a loading indicator
when work is currently running.
So in our blur activity, we should
observe a list of work infos.
Since we only need to know if the work is in progress,
we can use isFinished method.
isFinished returns true when the work has either succeeded,
failed, or been canceled.
So it's not a guarantee that the work was successfully done,
but it's good enough to let us know whether the work is still
in progress or not.
And with that final touch, our image blurring app
is in pretty good shape.
Now that we've learned how to schedule work,
let's take a quick look to understand how WorkManager
schedules these tasks.
Since WorkManager can schedule deferrable tasks that
don't need to run immediately, if your app is not currently
active, it's not a guarantee that your scheduled work
will be executed immediately.
Based on which apps standby bucket your app has
been placed in by the system, your scheduled work
may be deferred.
Since our image blurring app requires the user
to click a button in the app to start blurring the image,
the work will not be differed since the app is active
when the requested work is scheduled.
However, if our image blurring work
was scheduled to run periodically without a user
prompt, the work could be deferred based
on how often our app is used.
After a bit of work, we were able to create our first work
request to blur and save the new image.
Along the way, we learned how to chain requests,
to find unique work, and resolve potential conflicts.
Now that we've explored the basics of the WorkManager API,
it's time for you to give it a try in your own app.
We've added some links down in the video description
from our resources to help you get started.
And that concludes our work for now.
Be sure to tune into the next episode,
where we'll be looking at some more advanced usages
of WorkManager.
[MUSIC PLAYING]