Unable to update value of class variable from coroutine withContext(Dispatchers.Main.imidiate){}

I was working on a simple program where ,A a bound service is started and a coroutine is launched inside the service to generate random number every second and when get number button is clicked the number is displayed in a textView. The problem here is that I am unable to update the random number (default value is printed every time) here is my code

Main Activity

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import androidx.appcompat.app.AppCompatActivity
import com.example.androidservice.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var serviceIntent: Intent
    private lateinit var binding: ActivityMainBinding
    private lateinit var mService: MyService
    private var serviceConnected = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        serviceIntent = Intent(this, MyService::class.java)

        binding.startService.setOnClickListener {
            if (!serviceConnected) {
                startService(serviceIntent)
                bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)
            }
        }

        binding.stopService.setOnClickListener {
            if (serviceConnected) {
                unbindService(connection); stopService(serviceIntent)
            }
        }

        binding.stopRandom.setOnClickListener { if (serviceConnected) mService.stopCount() }

        binding.getNumber.setOnClickListener {
            if (serviceConnected) binding.displayNumber.text = mService.getNumber()
        }


    }


    private  val connection: ServiceConnection=object :ServiceConnection{

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder=service as MyService.MyBinding
            mService=binder.getService()
            serviceConnected=true

        }

        override fun onServiceDisconnected(name: ComponentName?) {
            serviceConnected=false
        }

    }

}

Service class

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
import kotlinx.coroutines.*

class MyService: Service() {

    private  var binder= MyBinding() as IBinder
    private lateinit var job:Job
    private var random1 =9

    class MyBinding: Binder() {
        fun getService():MyService{ return MyService() }
    }
    override fun onBind(intent: Intent?): IBinder {
          return binder
    }

    override fun onDestroy() {
        super.onDestroy()

        Log.i("Stop Self invoked","stopSelf()")
    }


    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("In service thread id= ",Thread.currentThread().name)

    job= CoroutineScope(Dispatchers.IO).launch {
            while (true) {
                val random = (1..100).random()
                Log.i("Random number is = ", random.toString())
             //   setValueToMainThread(random)
                withContext(Dispatchers.Main.immediate){                   // here is the problem code
                  [email protected]=random
                }
                delay(1000)
            }
        }

        return super.onStartCommand(intent, flags, startId)
    }


    fun stopCount(){ job.cancel()}
    fun getNumber():String {
      return  random1.toString()
    }

}

MainActivity XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/display_number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Number"
        android:textSize="32sp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.109" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Service"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.448" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/stop_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Service"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.485"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.543" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/stop_random"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Random"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.509"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.36" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/get_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="get number"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.657" />

</androidx.constraintlayout.widget.ConstraintLayout>

On debugging I found that the value of random1 was changing but the instance of MyService class is not the same when getNumber() is called from onClick. I can’t figure out what the problem is. I created a static variable to hold the instance of MyService then call it inside the coroutine. But is there any other way I can update the value of random1?

Answer

The instance of service you started and instance of service you are using to get number are different because of

fun getService():MyService{ return MyService() }

getService() will return new instance of MyService class everytime you call it.

Change your MyBinding class to inner class and return MyService instance from the method.

inner class MyBinding: Binder() {
    fun getService():MyService{ return [email protected] }
}