Search code examples
androidkotlinkotlin-coroutinesandroid-workmanager

Live data observer inside CoroutineWorker


I have an Worker that executed periodically. It connects to BLE device and syncs data from it. The connection is done by observers. doWork calling syncRides(). syncRides created an observeForever, and starts connections, when connection is established BleClient.runBleSync() called.

My concerns are the "observeForever" called every 15 min (minimal WorkManager time) and crerates observeForever that not removed. The thing is BleWorker does not have LifecycleOwner for creating "BleClient.connectionStatus.observe" instead of "BleClient.connectionStatus.observeForever". My question is should I be concerned of using observeForever and triggering it every 15 min. Or maybe you can suggest better option like adding and removing observer.

Also, when running without GlobalScope.launch(Dispatchers.Main) there is an error that this function cant run on background thread. So what does Dispatchers.Main mean when running in Worker?

class BleWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) {

override suspend fun doWork(): Result {

    return try {
        try {
            RLog.d("Run work manager")
            syncRides()
            val output: Data = workDataOf("KEY_RESULT" to 1)
            Result.success(output)
        } catch (e: Exception) {
            RLog.d("exception in doWork ${e.message}")
            Result.failure()
        }
    } catch (e: Exception) {
        RLog.d("exception in doWork ${e.message}")
        Result.failure()
    }
}


private suspend fun syncRides() {

    GlobalScope.launch(Dispatchers.Main) {

        val bleDevice = SharedPreferenceHelper.getBleMac()
        if (bleDevice != null && BleClient.connectionStatus.value == BleClient.ConnectionStatus.NOT_CONNECTED) {
            BleClient.connect(bleDevice)
        }

        BleClient.connectionStatus.observeForever {
            RLog.d("Observing $it")

            when (it) {
                BleClient.ConnectionStatus.CONNECTED -> {
                    GlobalScope.launch(Dispatchers.IO) {
                        RLog.d("Running sync")
                        BleClient.runBleSync()
                    }
                }
                else -> {
                    RLog.d("No status")
                }
            }
        }
    }
}

BleClient:

 object BleClient {

 val connectionStatus = MutableLiveData(ConnectionStatus.NOT_CONNECTED)

 fun connect(mac: String) {
//do some magic         
 connectionStatus.postValue(ConnectionStatus.CONNECTED)
 }
}

Solution

  • I assume that the app connect to another device on bluetooth any sync data. If my assumption is correct, first of all, you should offload the syncing process to a foreground service because the process would take a long time. Still you can use WorkManager for scheduling purposes. In the Foreground service, you should connect BLE and sync data. For this end, there are different options. If you need to use observable to observe connection status, you should use MutableSharedFlow rather than MutableLiveData thus you can observe changes with a lifecycle scope you created in the Service class. However, in my opinion, a better practice is that converting your connect() function into a suspendable function. For this you can use suspendCoroutine builder for the conversion. Additionally, if you call connect function from different threads simultaneously, you have to use lock to avoid multiple connection. Kotlin Coroutines has non-blocking locks for this. After converting your connect() function to a suspend one, you can implement your logic in linear fashion which is easy and doesn't require any kind of observation.