Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Showing detailed progress for running WorkManager workers

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.

like image 782
Andre Artus Avatar asked May 26 '18 11:05

Andre Artus


2 Answers

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

like image 100
Paulo Pereira Avatar answered Sep 28 '22 17:09

Paulo Pereira


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.

like image 24
Rahul Avatar answered Sep 28 '22 17:09

Rahul