How can I do work with a coroutine in a foreground service? Kotlin Android

I am trying to build an app that will run in a foreground service and give feedback to the user only through audio.

I have followed this example: https://betterprogramming.pub/what-is-foreground-service-in-android-3487d9719ab6 and it works as advertised: It shows with a TextView whether the service is running or not. And there is status bar notification. Great! Except there is no work being done in the background. So as a test, I have tried to implement making a toast every 1 seconds, but the app crashes when it runs the withContext(Main) line.

How can I fix this?

package com.sample.sampleforegroundservice
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.sample.sampleforegroundservice.MainActivity.Companion.ACTION_STOP_FOREGROUND
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Dispatchers.Main
class SampleForegroundService :Service() {
    private var wakeLock: PowerManager.WakeLock? = null
    private var isServiceStarted = false
    override fun onBind(intent: Intent?): IBinder? { return null }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        if (intent?.action != null && intent.action.equals(
                ACTION_STOP_FOREGROUND, ignoreCase = true)) {
            stopForeground(true)
            stopService()
            stopSelf()
        }
        generateForegroundNotification()
        startService()
        return START_STICKY
    }
    private var iconNotification: Bitmap? = null
    private var notification: Notification? = null
    var mNotificationManager: NotificationManager? = null
    private val mNotificationId = 123
    private fun generateForegroundNotification() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val intentMainLanding = Intent(this, MainActivity::class.java)
            val pendingIntent =
                PendingIntent.getActivity(this, 0, intentMainLanding, 0)
            iconNotification = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
            if (mNotificationManager == null) {
                mNotificationManager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                assert(mNotificationManager != null)
                mNotificationManager?.createNotificationChannelGroup(
                    NotificationChannelGroup("chats_group", "Chats")
                )
                val notificationChannel =
                    NotificationChannel("service_channel", "Service Notifications",
                        NotificationManager.IMPORTANCE_MIN)
                notificationChannel.enableLights(false)
                notificationChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
                mNotificationManager?.createNotificationChannel(notificationChannel)
            }
            val builder = NotificationCompat.Builder(this, "service_channel")
            builder
                .setContentTitle(StringBuilder(resources.getString(R.string.app_name))
                .append(" service is running").toString())
                .setTicker(StringBuilder(resources.getString(R.string.app_name))
                .append("service is running").toString())
                .setContentText("Touch to open") //                    , swipe down for more options.
                .setSmallIcon(R.drawable.ic_alaram)
                .setPriority(NotificationCompat.PRIORITY_LOW)
                .setWhen(0)
                .setOnlyAlertOnce(true)
                .setContentIntent(pendingIntent)
                .setOngoing(true)
            if (iconNotification != null) {
                builder.setLargeIcon(Bitmap.createScaledBitmap(iconNotification!!, 128, 128, false))
            }
            builder.color = resources.getColor(R.color.purple_200)
            notification = builder.build()
            startForeground(mNotificationId, notification)
        }
    }
    private fun startService(){
        if (isServiceStarted) return
        isServiceStarted = true
        CoroutineScope(Default).launch() {
            while (isServiceStarted) {
                task()
                delay(1000)
            }
        }
    }
    private fun stopService() {
        try {
            wakeLock?.let {
                if (it.isHeld) {
                    it.release()
                }
            }
            stopForeground(true)
            stopSelf()
        } catch (e: Exception) {
        }
        isServiceStarted = false
        //setServiceState(this, ServiceState.STOPPED)
    }
    private suspend fun task(){
        makeToastOnMainThread("Hello from the background")
    }
    private suspend fun makeToastOnMainThread(input: String) {
        withContext(Main) {
            //makeToast(input)
        }
    }
    private fun makeToast(input: String){
        Toast.makeText(this, input, Toast.LENGTH_SHORT).show()

    }
}
package com.sample.sampleforegroundservice
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.TextView
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.btn_start)?.setOnClickListener {
            startService(Intent(this, SampleForegroundService::class.java))
            updateTextStatus()
        }
        findViewById<View>(R.id.btn_stop)?.setOnClickListener {
            val intentStop = Intent(this, SampleForegroundService::class.java)
            intentStop.action = ACTION_STOP_FOREGROUND
            startService(intentStop)
            Handler().postDelayed({
                updateTextStatus()
            },100)
        }
        updateTextStatus()
    }
    private fun updateTextStatus() {
      if(isMyServiceRunning(SampleForegroundService::class.java)){
          findViewById<TextView>(R.id.txt_service_status)?.text = "Service is Running"
      }else{
          findViewById<TextView>(R.id.txt_service_status)?.text = "Service is NOT Running"
      }
    }
    private fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
        try {
            val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
            for (service in manager.getRunningServices( Int.MAX_VALUE )) {
                if (serviceClass.name == service.service.className) { return true }
            }
        } catch (e: Exception) {
            return false
        }
        return false
    }
    companion object{
        const val  ACTION_STOP_FOREGROUND = "${BuildConfig.APPLICATION_ID}.stopforeground"
    }
}

Leave a Comment