Search code examples
androidkotlinkotlinx.coroutines

Switching to UI context in coroutines


I'm new to coroutines and I'm wondering if it's possible to switch from coroutineScope (GlobalScope) to UI scope for the code below. My problem is that the steps inside the coroutine launch body must be executed in a worker thread, otherwise the listener notification must be executed in the ui thread in order to avoid to call runOnUiThread in my activity code.

override suspend fun startRent(name: String, bikeMode: BikeMode, listener: StartRentListener) {
        var bleDevice : RxBleDevice
        val scanFilter: ScanFilter = ScanFilter.Builder().setDeviceName(name).build()
        val scanSettings: ScanSettings = ScanSettings.Builder().build()

        val job = GlobalScope.launch {
            try {
                bleDevice = rxBleClient.scanBleDevicesExt(rxBleClient, scanSettings, scanFilter)
                val bleConnection = bleDevice.establishConnectionExt()
                // write handshake
                connectionManager.writeHandshake(bleDevice, bleConnection)
                // open lock
                openLock(bleDevice, bikeMode, bleConnection)
                // getting user position
                apiHelper.sendLockRequest(bleDevice.name, getPosition())
                bleDevice.disconnect()
                // this should be called on main thread once all the previous operations are finished
                listener.onSuccess()
            } catch (e: Exception) {
                listener.onError(e)
            }
        }
        job.join()
    }

A snippet of my current activity code:

bikeAccessClient.startRent(bikeBLEName, BikeMode.HYBRID, object :
                    StartRentListener {
                    override fun onSuccess() {
                        runOnUiThread {
                            // UI update here
                        }
                    }

Solution

  • You may use withContext(Dispatchers.Main) {..} function to execute a part of your code with the other Coroutine Dispatcher.

    kotlinx.coroutines.android contains the definition of the Dispatchers.Main function and it integrates correctly with Android UI.

    Using explicit Dispatcher in your code is quite error-prone. Instead, I would recommend designing the code with fewer explicit requirements.

    I would wrote something like that:

    fun uiActionHandlerToStartTheProcess() {
      launch(Dispatchers.Main) {
         val result = startRent(...) // no callback here, suspend function
         
         //UI Update Here
       }
    }
    suspend fun CoroutineScope.startRent() : SomeResultOfWork {
       //that function offloads the execution to a IO (aka brackground) thread
       return withContext(Dispatchers.IO){
         //here goes your code from `startRent`
         //use `suspendCancellableCoroutine {cont -> .. }` if you need to handle callbacks from it
        
         SomeResultOfWork()
       } 
    
    

    The code in the launch(Dispatchers.Main){..} block is executed in the UI thread. The call to startRent suspend function suspends the execution in the UI thread. Once the startRent is ready with the reply (from a background thread) it resumes the execution (which is done by the Dispatchers.Main and equivalent to the runOnUiThread {...}) and executes the UI update from the right thread