Как получить ViewModel из класса Worker?
- у меня есть
Activivity
в моем приложении называется MainActivity. MainActivity связано с
ViewModel
в методе OnCreate... val someViewModel = ViewModelProviders.of(this).get(SomeViewModel::class.java) ...
Я делаю некоторую фоновую работу с
Workers
,val someWork = OneTimeWorkRequestBuilder<LoginWorker>().build() WorkManager.getInstance().beginUniqueWork("SOMEWORK", ExistingWorkPolicy.REPLACE, captivePortalWork).enqueue()
Здесь возникает проблема...
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".
б) Проблема здесь, вероятно, может быть решена с помощью " Наблюдения за продвижением промежуточных рабочих".