[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]