Showing detailed progress for running WorkManager workers
Asked Answered
N

2

8

I want to replace the job scheduling aspect of my existing data syncing system with the new JetPack WorkManager (link to codelabs) component (in a sandbox branch of the app). My existing system works well but some of the new features in WorkManager would come in handy (e.g. chaining).

My current system uses a shared LiveData to communicate the progress from a job in progress to any UI element (RecyclerView in my case) observing on it (I'm actually SwitchMapping in the ViewModel into a list of SyncItems)

data class SyncItem(
        val title: String,
        private var _progress: Int,
        var total: Int) : BaseObservable() {

    var progress: Int
        @Bindable get() = _progress
        set(value) {
            _progress = value
            notifyPropertyChanged(BR.progress)
        }
}

The new WorkManager component has several methods (getStatusById, getStatusesByTag, etc.) that can be used to retrieve a LiveData with one or more WorkStatuses, but these only report a course-grained status (running, success, failed, cancelled).

What is the recommended way of communicating progress (e.g. '546/1234 items downloaded') to the UI? The setOutputData/getOutputData pair seems to be used more to communicate between Workers (which I need when chaining) than with the UI.

Attached is a screenshot of what it looks like (in a [test] version of my app using my old method) when a user opens the sync status page (2 items completed, rest in progress).

screenshot of syncing in progress

In the final product the user will be able to cancel any jobs in progress and re-issue once-off work requests. Normally the jobs will be fired off by PeriodicWorkRequest.

Nutria answered 26/5, 2018 at 11:23 Comment(0)
S
8

The best way to do it is to write intermediate progress to your own data store and expose a LiveData<>. We are considering adding this feature in a future alpha.

Strangulate answered 26/5, 2018 at 21:51 Comment(5)
Thanks Rahul, in a sense that is what I'm doing with my current method, except I'm using a singleton object as data store. I probably need to persist the progress data (I assume WM can kick off when app is closed). It would be nice to have this as official feature.Nutria
This is actually easy. I just added a table with a key, name, progress, total, and queries to set total (and clear progress) and to update progress. Can expand on it later.Nutria
A feature like this would be much appreciated, the WorkManager is best API Google has produced for Android to date. More features for it would be awesome.Asthenosphere
I also would be very interested in this, kind of the same usecase: I want to send the download progress to an observerDifferentia
This solution does not make sense anymore. Progress report is now available natively. See answer below.Resa
R
12

Natively Supported

implementation 'androidx.work:work-runtime:2.5.0'

Report progress on Worker:

public class FooWorker extends Worker {

    public FooWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        try {
            setProgressAsync(new Data.Builder().putInt("progress", 0).build());
            Thread.sleep(1000);
            setProgressAsync(new Data.Builder().putInt("progress", 50).build());
            Thread.sleep(1000);
            setProgressAsync(new Data.Builder().putInt("progress", 100).build());

            return Result.success();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return Result.failure();
        }
    }
}

Observe progress of Worker:

WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("test").observe(lifecycleOwner, new Observer<List<WorkInfo>>() {
        @Override
        public void onChanged(List<WorkInfo> workInfos) {
            if (workInfos.size() > 0) {
                WorkInfo info = workInfos.get(0);
                int progress = info.getProgress().getInt("progress", -1);
                //Do something with progress variable
            }

        }
    });

ListenableWorker now supports the setProgressAsync() API, which allows it to persist intermediate progress. These APIs allow developers to set intermediate progress that can be observed by the UI. Progress is represented by the Data type, which is a serializable container of properties (similar to input and output, and subject to the same restrictions).

See Android Documentation

Resa answered 24/9, 2019 at 13:8 Comment(0)
S
8

The best way to do it is to write intermediate progress to your own data store and expose a LiveData<>. We are considering adding this feature in a future alpha.

Strangulate answered 26/5, 2018 at 21:51 Comment(5)
Thanks Rahul, in a sense that is what I'm doing with my current method, except I'm using a singleton object as data store. I probably need to persist the progress data (I assume WM can kick off when app is closed). It would be nice to have this as official feature.Nutria
This is actually easy. I just added a table with a key, name, progress, total, and queries to set total (and clear progress) and to update progress. Can expand on it later.Nutria
A feature like this would be much appreciated, the WorkManager is best API Google has produced for Android to date. More features for it would be awesome.Asthenosphere
I also would be very interested in this, kind of the same usecase: I want to send the download progress to an observerDifferentia
This solution does not make sense anymore. Progress report is now available natively. See answer below.Resa

© 2022 - 2024 — McMap. All rights reserved.