Button to "toggle" a foreground service (start if stopped, stop if started)

from the CommonsWare Community archives

At October 28, 2020, 11:47am, fstanis asked:

I’m using a foreground service to persist a notification, but only if the user enables it by pressing a button in my app. I couldn’t find an easy way to do this, so any advice on whether my approach is flawed would be super useful.

In my fragment I have:

fun toggleService() {
    val intent = Intent(requireContext(), MyService::class.java)
    intent.action = MyService.ACTION_TOGGLE
    startForegroundService(requireContext(), intent)
}

And in MyService I decide if the service should start or stop inside onStartCommand:

class MyService : LifecycleService() {
    companion object {
        const val ACTION_TOGGLE = "toggle"
        const val NOTIFICATION_ID = 1
    }

    private var started = false

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent?.action == ACTION_TOGGLE) {
            if (!started) {
                startForegroundService()
            } else {
                stopForegroundService()
            }
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun stopForegroundService() {
        started = false
        stopForeground(true)
        stopSelf()
    }

    private fun startForegroundService() {
        val notification = createNotification(...)
        startForeground(NOTIFICATION_ID, notification)
        started = true
    }

    ...
}

Is there a better way to do this? Are there any corner cases that I’m potentially missing with my implementation (e.g. a race condition)?


At October 28, 2020, 12:59pm, mmurphy replied:

I worry a bit that calling startForegroundService() for your started scenario might result in “you did not call startForeground() within N seconds” errors on some versions of Android. But, if you are not seeing that on, say, Android 8.0 and 8.1, you should be OK. I forget all the rules for foreground services on modern versions of Android.

That depends a bit on when and how you call toggleService(). If you call it twice in rapid succession, while you will still get the onStartCommand() calls, I worry a bit about calling stopForeground() almost immediately after startForeground(). But, again, that’s a matter of testing, and it may be that you are in a situation where toggleService() will not be called that rapidly.


At October 28, 2020, 3:17pm, fstanis replied:

Got it, I’ll investigate what changes in older versions regarding this.

Is there a good way to check if the service is running from my fragment then (i.e. avoid startForegroundService(...) there if it is)? I could keep the started state in the service’s companion object, but this sounds more hacky.


At October 28, 2020, 6:03pm, mmurphy replied:

Since services are natural singletons, something in static scope could work, at least in theory. I get nervous about that sort of thing.

Or, you could call stopService() for the case where the toggle state is now “off”, rather that using startForegroundService() and asking the service to stop itself.