Introduction
When working with Services on Android, you might have ran into an issue where you would like to:
- Use a concrete implementation of a Service (from a built-in or third party library).
- Make the Service lifecycle-aware so that you can use coroutines with it. Many built-in Service classes are in Java and were introduced pre-Kotlin era.
As of right now, the only built-in Service class from Android that implements LifecycleOwner is LifecycleService. But LifecycleService is a concrete implementation, so you cannot extend both from it and another concrete Service.
We can opt in to use composition by having a LifecycleService as a member of our own Service, but there are couple of downsides to this approach:
- We will have to be very careful to pass the correct lifecycle calls to the member LifecycleService.
- Other components are not aware that our Service is now lifecycle-aware.
- I have not seen this pattern mentioned anywhere, so this approach is not well documented and is most likely anti-pattern.
- There is a better approach, which is to implement LifecycleOwner directly. This option is officially supported by Android.
In this tutorial, we will learn how to implement LifecycleOwner in our own Service.
Goals
At the end of the tutorial, you would have learned:
- How to implement LifecycleOwner in a Service.
Tools Required
- Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 2.
Prerequisite Knowledge
- Intermediate Android.
- Kotlin.
- Android Services.
Project Setup
To follow along with the tutorial, perform the steps below:
-
Create a new Android project with the default Empty Activity.
-
Add the dependencies below to your Module build.gradle.
implementation 'androidx.lifecycle:lifecycle-service:2.4.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
-
Create a CustomLibraryService class and extend Service.
//Making this open to simulate a library class open class CustomLibraryService : Service() { //return null for tutorial only. override fun onBind(p0: Intent?): IBinder? = null }
-
Create a new MyLifecycleService class from the code below, extending another Service and implementing LifecycleOwner interface as well. Ignore compile errors for now.
class MyLifecycleService: CustomLibraryService(), LifecycleOwner { }
ServiceLifecycleDispatcher
To be able to implement LifecycleOwner, we will use a class called ServiceLifecycleDispatcher. All of the methods provided by this class are important. The methods declared in this class are:
getLifecycle()
onServicePreSuperOnBind()
onServicePreSuperOnCreate()
onServicePreSuperOnDestroy()
onServicePreSuperOnStart()
All four of the onServicePre*()
methods must be called BEFORE the super.*()
calls in MyLifecycleService. The corresponding methods that we must override in MyLifecycleService are:
getLifecycle()
(from LifecycleOwner interface, not Service).onBind()
.onCreate()
.onStart()
(Deprecated on newer Android versions).onStartCommand()
.onDestroy
.
Later on, after implementing LifecycleOwner within our MyLifecycleService, the compiler will only prompt us to override getLifecycle()
, but that is not enough. If we want our coroutines to work properly, we must also override onBind()
, onCreate()
, onStart()
, onStartCommand()
, and onDestroy()
.
Add a ServiceLifecycleDispatcher as a member of MyLifecycleService using the code below. Ignore compile errors.
private val mServiceLifecycleDispatcher = ServiceLifecycleDispatcher(this)
Implement LifecycleOwner
LifecyleOwner only has one required method to override, getLifecycle()
, and we will deploy the helper class ServiceLifecycleDispatcher for this. Copy and paste the code below to implement getLifecycle()
.
override fun getLifecycle() = mServiceLifecycleDispatcher.lifecycle
Override the rest of the methods
Now, we will need to override all four on*()
methods that MyLifecycleService inherited from Service() (via CustomLibraryService). Follow the steps below. Note that we call according mServiceLifecycleDispatcher.onServicePre*()
methods before super.on*()
for all of them.
-
Override
onBind()
.override fun onBind(p0: Intent?): IBinder? { mServiceLifecycleDispatcher.onServicePreSuperOnBind() return super.onBind(p0) }
-
Override
onCreate()
.override fun onCreate() { mServiceLifecycleDispatcher.onServicePreSuperOnCreate() super.onCreate() }
-
Override
onStart()
.//Deprecated, but you might need to add this if targeting really old API. override fun onStart(intent: Intent?, startId: Int) { mServiceLifecycleDispatcher.onServicePreSuperOnStart() super.onStart(intent, startId) }
-
Override
onStartCommand()
.override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { mServiceLifecycleDispatcher.onServicePreSuperOnStart() return super.onStartCommand(intent, flags, startId) }
-
Override
onDestroy()
.override fun onDestroy() { mServiceLifecycleDispatcher.onServicePreSuperOnDestroy() super.onDestroy() }
And that is it. We have successfully implemented both LifecycleOwner and extended another concrete Service. As you can see, we can use coroutine and observe from LiveData as well. Here is a sample.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
mServiceLifecycleDispatcher.onServicePreSuperOnStart()
lifecycleScope.launch { }
/* Sample LiveData Usage
data.observe(this){
//Do work
}*/
return super.onStartCommand(intent, flags, startId)
}
Solution Code
CustomLibraryService.kt
package com.example.daniwebimplementlifecycleownerinservice
import android.app.Service
import android.content.Intent
import android.os.IBinder
//Making this open to simulate a library class
open class CustomLibraryService : Service() {
//return null for tutorial only.
override fun onBind(p0: Intent?): IBinder? = null
}
MyLifecycleService.kt
package com.example.daniwebimplementlifecycleownerinservice
import android.content.Intent
import android.os.IBinder
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ServiceLifecycleDispatcher
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MyLifecycleService: CustomLibraryService(), LifecycleOwner {
private val mServiceLifecycleDispatcher = ServiceLifecycleDispatcher(this)
override fun getLifecycle() = mServiceLifecycleDispatcher.lifecycle
override fun onBind(p0: Intent?): IBinder? {
mServiceLifecycleDispatcher.onServicePreSuperOnBind()
return super.onBind(p0)
}
override fun onCreate() {
mServiceLifecycleDispatcher.onServicePreSuperOnCreate()
super.onCreate()
}
//Deprecated, but you might need to add this if targeting really old API.
override fun onStart(intent: Intent?, startId: Int) {
mServiceLifecycleDispatcher.onServicePreSuperOnStart()
super.onStart(intent, startId)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
mServiceLifecycleDispatcher.onServicePreSuperOnStart()
lifecycleScope.launch { }
/* Sample LiveData Usage
data.observe(this){
//Do work
}*/
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
mServiceLifecycleDispatcher.onServicePreSuperOnDestroy()
super.onDestroy()
}
}
Module build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.daniwebimplementlifecycleownerinservice"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.lifecycle:lifecycle-service:2.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Summary
The full project code can be found at https://github.com/dmitrilc/DaniwebImplementLifecycleOwnerInService