Monitoring Work
WorkManager
does not provide a built-in means for you to monitor progress inside of an individual piece of work. It does, however, provide you with an API for monitoring the gross state changes of a piece of work: is it enqueued, is it running, is it completed, etc.
Getting the Status Updates
To find out about the general state changes in the life of a piece of work, you can use getWorkInfoByIdLiveData()
, available on WorkManager
. Each request has an ID, generated by the WorkManager
system, which you get by calling getId()
on the request:
final LiveData<WorkInfo> liveOpStatus=
WorkManager.getInstance().getWorkInfoByIdLiveData(downloadWork.getId());
The LiveData
that we get back will emit WorkInfo
updates for the work identified by this ID. A WorkInfo
, in turn, holds a State
enum, that indicates what phase of the WorkManager
process this piece of work is in:
ENQUEUED
-
BLOCKED
(for use with chained work) RUNNING
SUCCEEDED
FAILED
-
CANCELED
(for use with canceling work)
You can then arrange to observe the LiveData
or otherwise make use of its updates.
Consuming the Status Updates… In Code
The code shown in this chapter so far that created the OneTimeWorkRequest
and enqueued the work is in a DownloadViewModel
:
package com.commonsware.android.work.download;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MediatorLiveData;
import android.arch.lifecycle.ViewModel;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
public class DownloadViewModel extends ViewModel {
public final MediatorLiveData<WorkInfo> liveWorkStatus=new MediatorLiveData<>();
public void doTheDownload() {
Constraints constraints=new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build();
OneTimeWorkRequest downloadWork=
new OneTimeWorkRequest.Builder(DownloadWorker.class)
.setConstraints(constraints)
.setInputData(new Data.Builder()
.putString(DownloadWorker.KEY_URL,
"https://commonsware.com/Android/Android-1_0-CC.pdf")
.putString(DownloadWorker.KEY_FILENAME, "oldbook.pdf")
.build())
.addTag("download")
.build();
WorkManager.getInstance().enqueue(downloadWork);
final LiveData<WorkInfo> liveOpStatus=
WorkManager.getInstance().getWorkInfoByIdLiveData(downloadWork.getId());
liveWorkStatus.addSource(liveOpStatus, workStatus -> {
liveWorkStatus.setValue(workStatus);
if (workStatus.getState().isFinished()) {
liveWorkStatus.removeSource(liveOpStatus);
}
});
}
}
The doTheDownload()
method will be called when the user clicks a button in the UI of MainActivity
. That triggers our creation of the work request.
DownloadViewModel
takes the MediatorLiveData
approach described in the chapter on LiveData
and data binding. Consumers of the DownloadViewModel
, such as our MainActivity
, have access to a liveWorkStatus
field that represents the outbound stream of work status updates. For each doTheDownload()
call, we chain the LiveData
for this individual download onto the MediatorLiveData
, removing it as a source once the State
reaches a terminal condition (isFinished()
, which will be true
for a State
of SUCCEEDED
, FAILED
, or CANCELED
).
The result is that our MainActivity
can observe liveWorkStatus
, without having to worry about individual LiveData
objects from individual download requests.
MainActivity
observes liveWorkStatus
and uses it to display a Toast
when the download is finished:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final DownloadViewModel vm=ViewModelProviders.of(this).get(DownloadViewModel.class);
binding=ActivityMainBinding.inflate(getLayoutInflater());
binding.setViewModel(vm);
binding.setLifecycleOwner(this);
setContentView(binding.getRoot());
vm.liveWorkStatus.observe(this, workStatus -> {
if (workStatus!=null && workStatus.getState().isFinished()) {
Toast.makeText(this, R.string.msg_done, Toast.LENGTH_LONG).show();
}
});
}
Consuming the Status Updates… In Data Binding
MainActivity
— and its activity_main
layout resource — use data binding. Partially, this is to get control to DownloadViewModel
when the user clicks a button. But we also want to disable the button while the download is going on, to reduce the likelihood of accidentally triggering multiple downloads.
To that end, we bind the DownloadViewModel
into the binding, as was shown in the chapter on LiveData
and data binding. The layout then has binding expressions both for android:onClick
and android:enabled
on its Button
:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.commonsware.android.work.download.DownloadViewModel" />
</data>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/download"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/btn_title"
android:onClick="@{() -> viewModel.doTheDownload()}"
android:enabled="@{viewModel.liveWorkStatus }"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
android:onClick
just calls doTheDownload
on the DownloadViewModel
. android:enabled
takes advantage of the LiveData
support in data binding, with the extra assistance of a BindingAdapter
:
@BindingAdapter("android:enabled")
public static void setEnabled(View v, WorkInfo info) {
if (info==null) {
v.setEnabled(true);
}
else {
v.setEnabled(info.getState().isFinished());
}
}
Here, we map the State
from a WorkInfo
to the boolean
value to use for the android:enabled
attribute. Basically, if the WorkInfo
is null
or is finished, the button is enabled, otherwise it is disabled. So, as the LiveData
emits new WorkInfo
objects, data binding takes each, calls this setEnabled()
method, and uses that to update the enabled state of the Button
.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.