Android: Workmanager CoroutineWorker 总是失败
Android: Workmanager CoroutineWorker is always failing
我目前的问题是,我的 Workmanager 总是失败,但我不知道为什么。实际上,我什至不想使用 Workmanager,但我不知道在网络丢失或根本没有网络时取消协程的更好方法。
我正在尝试做的事情:检查网络是否可用 -> 从 Cloud Firestore 下载集合 -> 显示进度条 -> 下载成功 -> 显示列表。我已经在没有 workmanager 和协程的情况下做到了这一点(没有检查网络可用性)
当前情况:Toast "Enqueue"
-> Progress Bar
-> Toast "Failed"
文档工作者
class DocumentWorker @WorkerInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val firebaseEntity: DocumentFirebaseRepository,
private val documentDao: DocumentDao,
private val networkMapper: DocumentNetworkMapper,
private val cacheMapper: DocumentCacheMapper
): CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Get Data from Cloud Firestore and map it to a DocumentCacheEntity to insert it to the database
val documentEntityList: List<DocumentFirebaseEntity> = firebaseEntity.getAllDocuments()
val documentCacheList: List<DocumentCacheEntity> = networkMapper.mapFromEntityList(documentEntityList)
documentDao.insert(documentCacheList)
// Get the inserted list from the DAO and map it to Documents
val cachedDocumentEntities: List<DocumentCacheEntity> = documentDao.getList()
val documents: List<Document> = cacheMapper.mapFromEntityList(cachedDocumentEntities)
// Convert List<Documents> to WorkData Object. Is this the correct way? I don't know...
val data = workDataOf("documents" to documents)
return Result.success(data)
}
}
视图模型
class DocumentViewModel @ViewModelInject constructor(
@ApplicationContext private val context: Context,
@Assisted private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val work = OneTimeWorkRequestBuilder<DocumentWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
val workInfo: LiveData<WorkInfo> = WorkManager.getInstance(context).getWorkInfoByIdLiveData(work.id)
fun setStateEvent(documentStateEvent: DocumentStateEvent) {
viewModelScope.launch {
when (documentStateEvent) {
is DocumentStateEvent.GetDocumentEvent -> {
WorkManager.getInstance(context).enqueue(work)
}
}
}
}
}
片段
@AndroidEntryPoint
class DocumentsFragment(private val documentListAdapter: DocumentListAdapter) : Fragment() {
private val documentViewModel: DocumentViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribeToWorker()
documentViewModel.setStateEvent(DocumentStateEvent.GetDocumentEvent)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return DataBindingUtil.inflate<FragmentDocumentsBinding>(inflater, R.layout.fragment_documents, container, false).apply {
adapter = documentListAdapter
}.root
}
private fun subscribeToWorker() {
documentViewModel.workInfo.observe(viewLifecycleOwner) {
when(it.state) {
WorkInfo.State.ENQUEUED -> requireContext().toast("ENQUEUED")
WorkInfo.State.RUNNING -> displayProgressBar(true)
WorkInfo.State.SUCCEEDED -> {
displayProgressBar(false)
// Here I want to get my List<Document> and submit it to my ListAdapter...
documentListAdapter.submitList(it.outputData.getString("documents") as MutableList<Document>)
}
WorkInfo.State.BLOCKED -> {
requireContext().toast("BLOCKED")
displayProgressBar(false)
}
WorkInfo.State.FAILED -> {
requireContext().toast("FAILED")
displayProgressBar(false)
}
WorkInfo.State.CANCELLED -> {
requireContext().toast("CANCELLED")
displayProgressBar(false)
}
}
}
}
应用
@HiltAndroidApp
class App : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
override fun getWorkManagerConfiguration(): Configuration =
Configuration.Builder().setWorkerFactory(workerFactory).build()
}
如果有更好的方法在没有 Workmanager 的情况下完成所有这些工作(例如,手动检查网络状态并在网络丢失时取消协程),请告诉我!
非常感谢您的帮助,谢谢
编辑
好的,我发现了错误,这里是堆栈跟踪:
Caused by: java.lang.IllegalArgumentException: Key documents has invalid type class java.util.ArrayList
at androidx.work.Data$Builder.put(Data.java:830)
at com.example.app.data.models.validator.DocumentWorker.doWork(DocumentWorker.kt:42)
at com.example.app.data.models.validator.DocumentWorker$doWork.invokeSuspend(Unknown Source:11)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
这可能是错误:val data = workDataOf("documents" to documents)
好的,我的问题已经解决了!如上所述,(不幸的是)不可能将 List<Document>
放入 Workmanager 输入数据中。所以我想出了这个解决方案:
工作经理
class DocumentWorker @WorkerInject @Singleton constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val firebaseEntity: DocumentFirebaseRepository,
private val documentDao: DocumentDao,
private val networkMapper: DocumentNetworkMapper,
): CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Get Data from Cloud Firestore and map it to a DocumentCacheEntity to insert it to the database
val documentEntityList: List<DocumentFirebaseEntity> = firebaseEntity.getAllDocuments()
val documentCacheList: List<DocumentCacheEntity> = networkMapper.mapFromEntityList(documentEntityList)
documentDao.insert(documentCacheList)
return Result.success()
}
}
视图模型
class DocumentViewModel @ViewModelInject constructor(
@ApplicationContext private val context: Context,
@Assisted private val savedStateHandle: SavedStateHandle,
private val documentDB: DocumentDao,
private val cacheMapper: DocumentCacheMapper
//private val documentRepository: DocumentRepository,
) : ViewModel() {
// Save State and Document List in a Livedata Object that can be observed from the fragment
private val _documentDataState: MutableLiveData<Status<List<Document>>> = MutableLiveData()
val documentState: LiveData<Status<List<Document>>> get() = _documentDataState
// Build the OnetimeWorkRequest
private val work = OneTimeWorkRequestBuilder<DocumentWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
// Get the workInfo asFlow to observer it in the viewModel
private val workInfo: Flow<WorkInfo> = WorkManager.getInstance(context).getWorkInfoByIdLiveData(work.id).asFlow()
fun setStateEvent(documentStateEvent: DocumentStateEvent) {
when (documentStateEvent) {
is DocumentStateEvent.GetDocumentEvent -> {
// Here we Enqueue the Workmanager
WorkManager.getInstance(context).enqueue(work)
// Now we will observe (collect) the workInfo
viewModelScope.launch {
workInfo.collect {
when(it.state) {
WorkInfo.State.ENQUEUED -> _documentDataState.postValue(Status.loading())
WorkInfo.State.RUNNING -> _documentDataState.postValue(Status.loading())
WorkInfo.State.SUCCEEDED -> {
// Document loaded successfully into db, so get it from there and post it to the livedata
val documentCacheEntityList = documentDB.getList()
val documentList = cacheMapper.mapFromEntityList(documentCacheEntityList)
_documentDataState.postValue(Status.success(documentList))
}
WorkInfo.State.BLOCKED -> _documentDataState.postValue(Status.failed("No Internet Connection"))
WorkInfo.State.FAILED -> _documentDataState.postValue(Status.failed("No Internet Connection"))
WorkInfo.State.CANCELLED -> _documentDataState.postValue(Status.failed("Loading cancelled"))
}
}
}
}
}
如果有更好的方法,请告诉我。但这现在应该有效!我目前遇到的唯一问题是,当约束未在 5 秒内完成时,我想 return Result.failed
。
编辑
我做了一个函数,converts
Workinfo 的结果到我自己的 Status 结果。
suspend inline fun observerWorkerState(workInfFlow: Flow<WorkInfo>): Flow<Status<Unit>> = flow {
workInfFlow.collect {
when (it.state) {
WorkInfo.State.ENQUEUED -> emit(Status.loading<Unit>())
WorkInfo.State.RUNNING -> emit(Status.loading<Unit>())
WorkInfo.State.SUCCEEDED -> emit(Status.success(Unit))
WorkInfo.State.BLOCKED -> emit(Status.failed<Unit>("Workmanager blocked"))
WorkInfo.State.FAILED -> emit(Status.failed<Unit>("Workmanager failed"))
WorkInfo.State.CANCELLED -> emit(Status.failed<Unit>("Workmanager cancelled"))
}
}
}
我目前的问题是,我的 Workmanager 总是失败,但我不知道为什么。实际上,我什至不想使用 Workmanager,但我不知道在网络丢失或根本没有网络时取消协程的更好方法。
我正在尝试做的事情:检查网络是否可用 -> 从 Cloud Firestore 下载集合 -> 显示进度条 -> 下载成功 -> 显示列表。我已经在没有 workmanager 和协程的情况下做到了这一点(没有检查网络可用性)
当前情况:Toast "Enqueue"
-> Progress Bar
-> Toast "Failed"
文档工作者
class DocumentWorker @WorkerInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val firebaseEntity: DocumentFirebaseRepository,
private val documentDao: DocumentDao,
private val networkMapper: DocumentNetworkMapper,
private val cacheMapper: DocumentCacheMapper
): CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Get Data from Cloud Firestore and map it to a DocumentCacheEntity to insert it to the database
val documentEntityList: List<DocumentFirebaseEntity> = firebaseEntity.getAllDocuments()
val documentCacheList: List<DocumentCacheEntity> = networkMapper.mapFromEntityList(documentEntityList)
documentDao.insert(documentCacheList)
// Get the inserted list from the DAO and map it to Documents
val cachedDocumentEntities: List<DocumentCacheEntity> = documentDao.getList()
val documents: List<Document> = cacheMapper.mapFromEntityList(cachedDocumentEntities)
// Convert List<Documents> to WorkData Object. Is this the correct way? I don't know...
val data = workDataOf("documents" to documents)
return Result.success(data)
}
}
视图模型
class DocumentViewModel @ViewModelInject constructor(
@ApplicationContext private val context: Context,
@Assisted private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val work = OneTimeWorkRequestBuilder<DocumentWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
val workInfo: LiveData<WorkInfo> = WorkManager.getInstance(context).getWorkInfoByIdLiveData(work.id)
fun setStateEvent(documentStateEvent: DocumentStateEvent) {
viewModelScope.launch {
when (documentStateEvent) {
is DocumentStateEvent.GetDocumentEvent -> {
WorkManager.getInstance(context).enqueue(work)
}
}
}
}
}
片段
@AndroidEntryPoint
class DocumentsFragment(private val documentListAdapter: DocumentListAdapter) : Fragment() {
private val documentViewModel: DocumentViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribeToWorker()
documentViewModel.setStateEvent(DocumentStateEvent.GetDocumentEvent)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return DataBindingUtil.inflate<FragmentDocumentsBinding>(inflater, R.layout.fragment_documents, container, false).apply {
adapter = documentListAdapter
}.root
}
private fun subscribeToWorker() {
documentViewModel.workInfo.observe(viewLifecycleOwner) {
when(it.state) {
WorkInfo.State.ENQUEUED -> requireContext().toast("ENQUEUED")
WorkInfo.State.RUNNING -> displayProgressBar(true)
WorkInfo.State.SUCCEEDED -> {
displayProgressBar(false)
// Here I want to get my List<Document> and submit it to my ListAdapter...
documentListAdapter.submitList(it.outputData.getString("documents") as MutableList<Document>)
}
WorkInfo.State.BLOCKED -> {
requireContext().toast("BLOCKED")
displayProgressBar(false)
}
WorkInfo.State.FAILED -> {
requireContext().toast("FAILED")
displayProgressBar(false)
}
WorkInfo.State.CANCELLED -> {
requireContext().toast("CANCELLED")
displayProgressBar(false)
}
}
}
}
应用
@HiltAndroidApp
class App : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
override fun getWorkManagerConfiguration(): Configuration =
Configuration.Builder().setWorkerFactory(workerFactory).build()
}
如果有更好的方法在没有 Workmanager 的情况下完成所有这些工作(例如,手动检查网络状态并在网络丢失时取消协程),请告诉我!
非常感谢您的帮助,谢谢
编辑
好的,我发现了错误,这里是堆栈跟踪:
Caused by: java.lang.IllegalArgumentException: Key documents has invalid type class java.util.ArrayList
at androidx.work.Data$Builder.put(Data.java:830)
at com.example.app.data.models.validator.DocumentWorker.doWork(DocumentWorker.kt:42)
at com.example.app.data.models.validator.DocumentWorker$doWork.invokeSuspend(Unknown Source:11)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
这可能是错误:val data = workDataOf("documents" to documents)
好的,我的问题已经解决了!如上所述,(不幸的是)不可能将 List<Document>
放入 Workmanager 输入数据中。所以我想出了这个解决方案:
工作经理
class DocumentWorker @WorkerInject @Singleton constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val firebaseEntity: DocumentFirebaseRepository,
private val documentDao: DocumentDao,
private val networkMapper: DocumentNetworkMapper,
): CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Get Data from Cloud Firestore and map it to a DocumentCacheEntity to insert it to the database
val documentEntityList: List<DocumentFirebaseEntity> = firebaseEntity.getAllDocuments()
val documentCacheList: List<DocumentCacheEntity> = networkMapper.mapFromEntityList(documentEntityList)
documentDao.insert(documentCacheList)
return Result.success()
}
}
视图模型
class DocumentViewModel @ViewModelInject constructor(
@ApplicationContext private val context: Context,
@Assisted private val savedStateHandle: SavedStateHandle,
private val documentDB: DocumentDao,
private val cacheMapper: DocumentCacheMapper
//private val documentRepository: DocumentRepository,
) : ViewModel() {
// Save State and Document List in a Livedata Object that can be observed from the fragment
private val _documentDataState: MutableLiveData<Status<List<Document>>> = MutableLiveData()
val documentState: LiveData<Status<List<Document>>> get() = _documentDataState
// Build the OnetimeWorkRequest
private val work = OneTimeWorkRequestBuilder<DocumentWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
// Get the workInfo asFlow to observer it in the viewModel
private val workInfo: Flow<WorkInfo> = WorkManager.getInstance(context).getWorkInfoByIdLiveData(work.id).asFlow()
fun setStateEvent(documentStateEvent: DocumentStateEvent) {
when (documentStateEvent) {
is DocumentStateEvent.GetDocumentEvent -> {
// Here we Enqueue the Workmanager
WorkManager.getInstance(context).enqueue(work)
// Now we will observe (collect) the workInfo
viewModelScope.launch {
workInfo.collect {
when(it.state) {
WorkInfo.State.ENQUEUED -> _documentDataState.postValue(Status.loading())
WorkInfo.State.RUNNING -> _documentDataState.postValue(Status.loading())
WorkInfo.State.SUCCEEDED -> {
// Document loaded successfully into db, so get it from there and post it to the livedata
val documentCacheEntityList = documentDB.getList()
val documentList = cacheMapper.mapFromEntityList(documentCacheEntityList)
_documentDataState.postValue(Status.success(documentList))
}
WorkInfo.State.BLOCKED -> _documentDataState.postValue(Status.failed("No Internet Connection"))
WorkInfo.State.FAILED -> _documentDataState.postValue(Status.failed("No Internet Connection"))
WorkInfo.State.CANCELLED -> _documentDataState.postValue(Status.failed("Loading cancelled"))
}
}
}
}
}
如果有更好的方法,请告诉我。但这现在应该有效!我目前遇到的唯一问题是,当约束未在 5 秒内完成时,我想 return Result.failed
。
编辑
我做了一个函数,converts
Workinfo 的结果到我自己的 Status 结果。
suspend inline fun observerWorkerState(workInfFlow: Flow<WorkInfo>): Flow<Status<Unit>> = flow {
workInfFlow.collect {
when (it.state) {
WorkInfo.State.ENQUEUED -> emit(Status.loading<Unit>())
WorkInfo.State.RUNNING -> emit(Status.loading<Unit>())
WorkInfo.State.SUCCEEDED -> emit(Status.success(Unit))
WorkInfo.State.BLOCKED -> emit(Status.failed<Unit>("Workmanager blocked"))
WorkInfo.State.FAILED -> emit(Status.failed<Unit>("Workmanager failed"))
WorkInfo.State.CANCELLED -> emit(Status.failed<Unit>("Workmanager cancelled"))
}
}
}