A foreground service is a background task that performs operations that are going to be shown to the user, so he is aware of updates, mainly through notifications. For example, if we are developing an Android application to keep track of the pomodoro technique, we want to show the user the time remaining or if he is currently in a working or rest pomodoro.

Once understood what a foreground service is, we are going to see first how we can implement the service, and then how to control its notifications to inform the user about what’s going on.

Implementing the foreground service

In order to be able to implement a foreground service, the first thing we need to do, is request permission to launch this kind of background task. For that, we just need to open our manifest file and add the following line:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Kotlin

In this case, we are adding an install-time permission, so there’s no need to ask the user to concede them to us, so we are done with this part.

Now, let’s create the class that is going to contain our service logic, for that we just need to extend the Service class. Don’t forget, as always, before creating a service, we need to go to the manifest, inside the <application> tag, to tell our application that a new service is going to be created. In this case, the class is call TimerService, so:

<service android:name=".TimerService"
    android:exported="false" />
Kotlin

Notice the exported attribute with false value, because for this example, we don’t want other apps to interact with the service

For the moment, our TimerService class is going to look like this.

class TimerService : Service() {

    private val binder: Binder = TimerBinder()

    override fun onBind(p0: Intent?): IBinder = binder

    inner class TimerBinder : Binder() {
        val service: TimerService
            get() = this@TimerService
    }
}
Kotlin

With just these lines of code, we have a working foreground service. Observe that we are overriding the onBind function, as we are going to need it to connect the service to different app components, like an activity.

There are other methods that we can override, for example, onStartCommand and onDestroy, that we can use to register and unregister a BroadcastReceiver that we could use to connect the service with actions that are triggered through buttons in our notification. If we were implementing a pomodoro app, we could pause or finish the current pomodoro with this.

Now that the service is set up, we need to launch it and bind it to the activity so that we can access the service. First, we need to implement a ServiceConnection, that is going to give us two callbacks for when the service is connected and disconnected.

private lateinit var timerService: TimerService
private var isTimerServiceBound: Boolean = false
private val timerServiceConnection = object : ServiceConnection {

        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            timerService = (binder as TimerService.TimerBinder).service
            isTimerServiceBound = true
        }

        override fun onServiceDisconnected(p0: ComponentName?) {
            isTimerServiceBound = false
        }
    }
Kotlin

And now, we can actually launch the service. The above code is going to give us two things, a TimerService instance, and a control variable to know when the service is bound to the activity. This is important because if the activity is destroyed, we want to eliminate this connection to save resources. We could also use the control variable to keep showing a splash screen until the service is loaded.

Removing the connection between the activity and the service doesn’t mean that the service is going to stop and be destroyed, so in this sample, if the activity finishes, the service is going to keep running.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        startService(Intent(this, TimerService::class.java))
        bindService(
            Intent(this, TimerService::class.java),
            timerServiceConnection,
            Context.BIND_AUTO_CREATE
        )

        setContent {
            // Some Jetpack Compose UI
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (isTimerServiceBound) {
            unbindService(timerServiceConnection)
        }
    }
Kotlin

Implementing the notifications

In Android, since the 8.0 version, we need to create what is known as a “Channel” to show notifications. So the first step to prepare our service for notifications, is creating the channel with some configuration. We can configure a vibration pattern, sound, name of the channel…but the most important thing here is the importance level of the notifications, which you can check here.

fun createChannel(
        context: Context,
        channelId = CHANNEL_ID,
        @StringRes channelName: Int,
        @StringRes channelDescription: Int,
        importanceLevel: Int = NotificationManager.IMPORTANCE_HIGH
    ) {
        val channel = NotificationChannel(
            context.getString(channelId),
            context.getString(channelName),
            importanceLevel
        ).apply {
            description = context.getString(channelDescription)
        }

        val notificationManager =
            context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
Kotlin

Once the notification channel is created, we can start showing notifications. In the notification we can set an icon (it’s mandatory that its content is black and the background transparent), a title, content of the notification and even buttons that can be connected with the service using Intents and BroadcastReceivers. We are going to see how to do the simplest notification, as I don’t want to make the tutorial too long, but if you are interested in how to use notification buttons to interact with the service, leave a comment, and I will create another post talking about it.

In this case, we want to post notifications that are going to replace older ones, and because of that, we need an ID. We can generate one for example like this.

private var id: Int = UUID.randomUUID().hashCode()
Kotlin

And finally, to show the notification:

fun createNotification(
        context: Context,
        title: String,
        content: String,
    ) {
        val builder = NotificationCompat.Builder(context, CHANNEL_ID)
        val notification = builder.setContentTitle(title).setContentText(content)
            .setSmallIcon(R.drawable.ic_launcher_foreground).build()
        NotificationManagerCompat.from(context).notify(id, notification)
    }
Kotlin

The last thing to do is call these functions from our service, for example, overriding the onStartCommand method.

We are done!

Easy right? Now we have a service that survives the lifetime of the app and shows notifications of what’s going on to the user. Don’t forget to leave any comments or ideas you want to add to the post, or check the repository with the project if you need to see more code.


If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.

Featured image by Jonas Leupe on Unsplash

Categorized in: