Search code examples
multithreadingfunctionkotlinreturnlooper

How do I get a function in another thread to start the next function in the main thread after it is done?


Here is, what I'm trying to do:

  1. A Switch is turned on, starting a service in another thread (works fine so far)
  2. When this service is successful, it should then start another function within the main thread

I don't mind whether the function is called directly by the service or the service is returning a "success"-value to the main thread, what then starts the next function from there.

Here is, what the important parts of the code looks like:

Main thread:

class SendNotif : AppCompatActivity() {

    val context = this

    private lateinit var Switch: Switch

        // Start LocationService when the switch is on
        Switch.setOnCheckedChangeListener { buttonView, isChecked ->
            if (isChecked) {
                Toast.makeText(context, "Starting LocationService", Toast.LENGTH_SHORT).show()
                Intent(applicationContext, LocationService::class.java).apply {
                    action = LocationService.ACTION_START
                    startService(this)
                }
            } else {
                Toast.makeText(context, "Stopping LocationService", Toast.LENGTH_SHORT).show()
                Intent(applicationContext, LocationService::class.java).apply {
                    action = LocationService.ACTION_STOP
                    startService(this)
                }
            }
        }
    }

    fun InitiateMessage() {
// This is the function, that is supposed to start after the LocationService
        }
    }

This is the LocationService. After being successful, the function InitiateMessage() should start.

class LocationService: Service() {

    private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private lateinit var locationClient: LocationClient

    var lat = 0.0F
    var long = 0.0F

    override fun onBind(p0: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        locationClient = DefaultLocationClient(
            applicationContext,
            LocationServices.getFusedLocationProviderClient(applicationContext)
        )
    }

    // Start or stop the service
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when(intent?.action) {
            ACTION_START -> start()
            ACTION_STOP -> stop()
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun start() {
        // Starting notification
        val notification = NotificationCompat.Builder(this, "location")
            .setContentTitle("Tracking location...")
            .setContentText("Location: null")
            .setSmallIcon(R.drawable.ic_launcher_background)
            // Can't swipe this notification away
            .setOngoing(true)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Starting the location updates
        locationClient
            // Every 10 seconds
            .getLocationUpdates(10000L)
            .catch { e -> e.printStackTrace() }
            .onEach { location ->
                lat = location.latitude.toString().toFloat() // .takeLast(3) // taking only the last 3 digits
                long = location.longitude.toString().toFloat() // .takeLast(3)
                val updatedNotification = notification.setContentText(
                    "Location: ($lat, $long)"
                )
                // notificationManager.notify(1, updatedNotification.build())

                // Geofence
                MyGeofence(lat, long)
            }
            .launchIn(serviceScope)

        // startForeground(1, notification.build())
    }

    private fun stop() {
        // Stopping the notification
        stopForeground(true)
        // Stopping the location service
        stopSelf()
    }

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel()
    }

    companion object {
        const val ACTION_START = "ACTION_START"
        const val ACTION_STOP = "ACTION_STOP"
    }

    fun MyGeofence(lat : Float, long : Float){

        val context = this
        var db = DataBaseHandler(context)
        var data = db.readData()

        // Setting the accuracy of the geofence
        val acc = 2

        val safelat : Double = data.get(0).LocLat.toFloat().round(acc)
        val safelong = data.get(0).LocLong.toFloat().round(acc) // .take(acc).take(acc)


        val h = Handler(context.mainLooper)

        if(safelat == lat.toFloat().round(acc) && safelong == long.toFloat().round(acc)){
            h.post(Runnable { Toast.makeText(context, "You have reached your safe refuge! " + lat.toFloat().round(acc) + " " + long.toFloat().round(acc), Toast.LENGTH_LONG).show() })

            // ToDo: Right hereafter the function InitiateMessage() should start

        }
        else{
            h.post(Runnable { Toast.makeText(context, "You are still in great danger! "  + lat.toFloat().round(acc) + " " + long.toFloat().round(acc), Toast.LENGTH_LONG).show() })
        }
    }

    fun Float.round(decimals: Int): Double {
        var multiplier = 1.0
        repeat(decimals) { multiplier *= 10 }
        return round(this * multiplier) / multiplier
    }

}

So far, I tried it with a Looper, which did not work.

java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-1,5,main] that has not called Looper.prepare()

But I guess the far easier way would be a returned value by the service. How do I implement this, and how do I start the next function through this returned value?


Solution

  • I solved my problem with an observe-function and a companion object, that is a MutableLiveData.

    The companion object is placed inside the main thread:

        companion object {
        // var iamsafe: Boolean = false
        val iamsafe: MutableLiveData<Boolean> by lazy {
            MutableLiveData<Boolean>()
        }
    }
    

    The observe-function is placed within onCreate:

    val safeObserver = Observer<Boolean> { newState ->
            Toast.makeText(context, "Initiating message to my mate.", Toast.LENGTH_SHORT).show()
            InitiateMessage()
        }
    
        iamsafe.observe(this, safeObserver)
    

    The companion is changed in the second thread like this:

    SendNotif.iamsafe.postValue (true)