Search code examples
androidkotlinkotlin-coroutinesandroid-jetpackandroid-workmanager

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


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.


Solution

  • 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
                    this@coroutineScope.cancel()
                } else if (finished) {
                    this@coroutineScope.cancel()
                }
            }
        } 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"))
            }
        }
    }