如何从 Worker class 获取 ViewModel?
How to get a ViewModel from a Worker class?
- 我的应用程序中有一个名为 MainActivity 的
Activivity
。
MainActivity 与 OnCreate 方法中的一个ViewModel
相关联
...
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
以显示后台工作的进度并每次更新 UI。
A) 我可以从 MainActivity 访问视图模型并观察 worker
但是我只能在进度成功或失败时更新 ViewModel
... 这意味着我可以在工作完成 0% 或 100% 时显示进度。
B) 如果我可以从 Worker class 访问 ViewModel,我可以随时更新 ViewModel。但是我不知道怎么...这个方法在 Worker class.
中不起作用
val someWork = ViewModelProviders.of(this).get(SomeViewModel::class.java)
如何从 Worker
class 得到 ViewModel
?
您不能为此使用 ViewModel
。您需要使用自己的房间数据库并向您的 UI 公开一个 LiveData
以显示进度。
您必须将 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;
}
}
Activity 的代码:
//...
@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
}
});
}
//...
来自非常好的 Google 文档的两个附加信息。
a) 如果您来这里是因为没有 viewmodelscope.launch 就无法 运行 您的任务,请查看 Android“Threading in WorkManager”文档。
b) 这里的问题大概可以用“Observing intermediate Worker progress”来解决。
简而言之,您不想将 ViewModel
暴露给 WorkManager
或 Worker
class,而是将百分比暴露给 ViewModel
.
WorkManager 2.3.1+ 添加了对设置和观察中间进度的支持。
For Java developers using a ListenableWorker or a Worker, the setProgressAsync() API returns a ListenableFuture; updating progress is asynchronous, given that the update process involves storing progress information in a database. In Kotlin, you can use the CoroutineWorker object's setProgress() extension function to update progress information.
对于 Kotlin,将 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
}
}
}
- 我的应用程序中有一个名为 MainActivity 的
Activivity
。 MainActivity 与 OnCreate 方法中的一个
ViewModel
相关联... 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
以显示后台工作的进度并每次更新 UI。A) 我可以从 MainActivity 访问视图模型并观察
worker
但是我只能在进度成功或失败时更新ViewModel
... 这意味着我可以在工作完成 0% 或 100% 时显示进度。B) 如果我可以从 Worker class 访问 ViewModel,我可以随时更新 ViewModel。但是我不知道怎么...这个方法在 Worker class.
中不起作用val someWork = ViewModelProviders.of(this).get(SomeViewModel::class.java)
如何从
Worker
class 得到ViewModel
?
您不能为此使用 ViewModel
。您需要使用自己的房间数据库并向您的 UI 公开一个 LiveData
以显示进度。
您必须将 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;
}
}
Activity 的代码:
//...
@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
}
});
}
//...
来自非常好的 Google 文档的两个附加信息。
a) 如果您来这里是因为没有 viewmodelscope.launch 就无法 运行 您的任务,请查看 Android“Threading in WorkManager”文档。
b) 这里的问题大概可以用“Observing intermediate Worker progress”来解决。
简而言之,您不想将 ViewModel
暴露给 WorkManager
或 Worker
class,而是将百分比暴露给 ViewModel
.
WorkManager 2.3.1+ 添加了对设置和观察中间进度的支持。
For Java developers using a ListenableWorker or a Worker, the setProgressAsync() API returns a ListenableFuture; updating progress is asynchronous, given that the update process involves storing progress information in a database. In Kotlin, you can use the CoroutineWorker object's setProgress() extension function to update progress information.
对于 Kotlin,将 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
}
}
}