Как получить ViewModel из класса Worker?

  1. у меня есть Activivity в моем приложении называется MainActivity.
  2. MainActivity связано с ViewModel в методе OnCreate

    ...
    val someViewModel = ViewModelProviders.of(this).get(SomeViewModel::class.java)
    ...
    
  3. Я делаю некоторую фоновую работу с Workers,

    val someWork = OneTimeWorkRequestBuilder<LoginWorker>().build() WorkManager.getInstance().beginUniqueWork("SOMEWORK", ExistingWorkPolicy.REPLACE, captivePortalWork).enqueue()

  4. Здесь возникает проблема... worker например, сделать цикл 10 раз, и каждый раз просто спать на секунду... так что у нас есть 10 секунд фоновой работы. Я хочу обновить ViewModel чтобы показать прогресс фоновой работы и обновлять пользовательский интерфейс каждый раз.

    A) Я могу получить доступ к модели View из MainActivity и наблюдать за worker но и я могу только обновить ViewModel когда прогресс достигнут или завершился неудачей... это означает, что я могу показать прогресс, когда работа выполнена на 0 или на 100%.

    Б) Если бы я мог получить доступ к ViewModel из класса Worker, я мог бы обновить ViewModel в любое время. Но я не знаю, как... этот метод не работает внутри класса Worker.

    val someWork = ViewModelProviders.of(this).get(SomeViewModel::class.java)

    Как я могу получить ViewModel из Worker учебный класс?

2 ответа

Вы должны назначить значение из Worker выход Data в ViewModelи эти данные наблюдаются у вас Activity, вот так:

код для работника:

public class MyWorker extends Worker {

    public static final String MY_KEY_DATA_FROM_WORKER = "MY_KEY_DATA_FROM_WORKER";

    public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }

    @NonNull
    @Override
    public Worker.Result doWork() {
        //here do http or database requests
        String result = "my needed result";

        if (result != null) {
            setOutputData(new Data.Builder()
                    .putString(MY_KEY_DATA_FROM_WORKER, result)
                    .build());

            return Worker.Result.SUCCESS;
        }

        return Result.FAILURE;
    }
}

код для ViewModel:

 public class MyActivityModel extends ViewModel {

    private final MutableLiveData<String> myLiveData = new MutableLiveData<String>();

    public void loadDataFromWorker(LifecycleOwner lifecycleOwner) {
        OneTimeWorkRequest myWorkerReq = new OneTimeWorkRequest.Builder(MyWorker.class)
                .build();

        WorkManager mWorkManager = WorkManager.getInstance();
        mWorkManager
                .beginWith(myWorkerReq)
                .enqueue();

        mWorkManager.getStatusByIdLiveData(myWorkerReq.getId()).observe(lifecycleOwner, new Observer<WorkStatus>() {
            @Override
            public void onChanged(WorkStatus workStatus) {
                if (workStatus.getState().isFinished()) {

                    // here you asign data to ViewModel
                    Data workOutputData = workStatus.getOutputData();
                    myLiveData.setValue(workOutputData.getString(MyWorker .MY_KEY_DATA_FROM_WORKER));
                }
            }
        });
    }

    public MutableLiveData<String> getData() {
        return myLiveData;
    }
  }

Код для активности:

//...

  @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.my_activity);

    //declare model and start loading data in Worker
    MyActivityModel model = ViewModelProviders.of(this).get(MyActivityModel.class);
    model.loadDataFromWorker(this);

    //observe data from model
    model.getData().observe(this, new Observer<String>() {
        @Override
        public void onChanged(String str) {

        //here you get new String value or null if Work failed 

        }
    });
}

//...

Короче говоря, вы не хотите подвергать ViewModel к WorkManager или Worker класс, но вместо этого выставьте процент своему ViewModel.

В WorkManager 2.3.1+ добавлена ​​поддержка для настройки и наблюдения за промежуточным прогрессом.

Для разработчиков Java, использующих ListenableWorker или Worker, API setProgressAsync() возвращает ListenableFuture; процесс обновления является асинхронным, поскольку процесс обновления включает в себя сохранение информации о ходе выполнения в базе данных. В Kotlin вы можете использовать функцию расширения setProgress () объекта CoroutineWorker для обновления информации о ходе выполнения.

Источник

Для Котлина используйте CoroutineWorker со следующим:

companion object {
    const val Progress = "Progress"
    private const val delayDuration = 1L
}

override suspend fun doWork(): Result {
    val firstUpdate = workDataOf(Progress to 0)
    val lastUpdate = workDataOf(Progress to 100)
    setProgress(firstUpdate)
    delay(delayDuration)
    setProgress(lastUpdate)
    return Result.success()
}

В вашем ViewModel, вы можете наблюдать за своим прогрессом следующим образом:

WorkManager.getInstance(applicationContext)
// requestId is the WorkRequest id
.getWorkInfoByIdLiveData(requestId)
.observe(observer, Observer { workInfo: WorkInfo? ->
        if (workInfo != null) {
            val progress = workInfo.progress
            val value = progress.getInt(Progress, 0)
            // Do something with progress information
        }
})

Если вы используете WorkContinuationи хотите получить общий процент, я обнаружил, что проще всего использовать workInfosLiveData нравится:

WorkContinuation.combine(workForSyncingFlights).also {
it.enqueue()
it.workInfosLiveData.asFlow().conflate().collect { listOfWorkInfo ->
    if (listOfWorkInfo.isNullOrEmpty()) return@collect
    listOfWorkInfo.forEach { workInfo ->
        if (workInfo.state.isFinished) {
            val finished = listOfWorkInfo.filter { info -> info.state.isFinished }.size
            val totalPercent = finished.toDouble().div(listOfWorkInfo.size.toDouble()) * 100
        }
    }
}

Вы не можете использовать ViewModel за это. Вы должны использовать свою собственную базу данных комнат и выставить LiveData на ваш пользовательский интерфейс, чтобы показать прогресс.

Две дополнительные информации из очень хорошей документации Google.

a) Если вы пришли сюда, потому что не можете запустить свою задачу без viewmodelscope.launch, взгляните на документацию Android "Threading in WorkManager".

б) Проблема здесь, вероятно, может быть решена с помощью " Наблюдения за продвижением промежуточных рабочих".