Reputation: 4702
I want to detect, how long a specific work is already in enqueue
mode. I need this information, in order to inform the user about his state (e.g when workmanager is longer than 10 seconds in enqueue mode
-> cancel work -> inform user that he needs to do X in order to achieve Y). Something like this:
workInfo.observe(viewLifecylceOwner) {
when(it.state) {
WorkInfo.State.ENQUEUED -> if(state.enqueue.time > 10) cancelWork()
}
}
I didn't find anything about this anywhere. Is this possible?
I appreciate every help.
Upvotes: 3
Views: 1755
Reputation: 4702
I have managed to create a somewhat robust "Workmanager watcher". My intention was the following: When the Workmanager is not finished within 7 seconds, tell the user that an error occurred. The Workmanager itself will never be cancelled, furthermore my function is not even interacting with the Workmanager itself. This works in 99% of all cases:
object WorkerHelper {
private var timeStamp by Delegates.notNull<Long>()
private var running = false
private var manuallyStopped = false
private var finished = false
open val maxTime: Long = 7000000000L
// Push the current timestamp, set running to true
override fun start() {
timeStamp = System.nanoTime()
running = true
manuallyStopped = false
finished = false
Timber.d("Mediator started")
}
// Manually stop the WorkerHelper (e.g when Status is Status.Success)
override fun stop() {
if (!running) return else {
running = false
manuallyStopped = true
finished = true
Timber.d("Mediator stopped")
}
}
override fun observeMaxTimeReachedAndCancel(): Flow<Boolean> = flow {
try {
coroutineScope {
// Check if maxTime is not passed with => (System.nanoTime() - timeStamp) <= maxTime
while (running && !finished && !manuallyStopped && (System.nanoTime() - timeStamp) <= maxTime) {
emit(false)
}
// This will be executed only when the Worker is running longer than maxTime
if (!manuallyStopped || !finished) {
emit(true)
running = false
finished = true
[email protected]()
} else if (finished) {
[email protected]()
}
}
} catch (e: CancellationException) {
}
}.flowOn(Dispatchers.IO)
Then in my Workmanager.enqueueWork function:
fun startDownloadDocumentWork() {
WorkManager.getInstance(context)
.enqueueUniqueWork("Download Document List", ExistingWorkPolicy.REPLACE, downloadDocumentListWork)
pushNotification()
}
private fun pushNotification() {
WorkerHelper.start()
}
And finally in my ViewModel
private fun observeDocumentList() = viewModelScope.launch {
observerWorkerState(documentListWorkInfo).collect {
when(it) {
is Status.Loading -> {
_documentDataState.postValue(Status.loading())
// Launch another Coroutine, otherwise current viewmodelscrope will be blocked
CoroutineScope(Dispatchers.IO).launch {
WorkerHelper.observeMaxTimeReached().collect { lostConnection ->
if (lostConnection) {
_documentDataState.postValue(Status.failed("Internet verbindung nicht da"))
}
}
}
}
is Status.Success -> {
WorkerHelper.finishWorkManually()
_documentDataState.postValue(Status.success(getDocumentList()))
}
is Status.Failure -> {
WorkerHelper.finishWorkManually()
_documentDataState.postValue(Status.failed(it.message.toString()))
}
}
}
}
I've also created a function that converts the Status of my workmanager
to my custom status class:
sealed class Status<out T> {
data class Success<out T>(val data: T) : Status<T>()
class Loading<T> : Status<T>()
data class Failure<out T>(val message: String?) : Status<T>()
companion object {
fun <T> success(data: T) = Success<T>(data)
fun <T> loading() = Loading<T>()
fun <T> failed(message: String?) = Failure<T>(message)
}
}
suspend inline fun observerWorkerState(workInfoFlow: Flow<WorkInfo>): Flow<Status<Unit>> = flow {
workInfoFlow.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"))
}
}
}
Upvotes: 1