Android: How to detect how long workmanager is already in enqueue mode?

1.6k views Asked by At

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:

Pseudo Code

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.

1

There are 1 answers

3
Andrew On BEST ANSWER

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:

Workerhelper

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:

Status

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)
    }
}

Function

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"))
        }
    }
}