Android Native - How to implement LifecycleOwner in a Service

dimitrilc 1 Tallied Votes 2K Views Share

Introduction

When working with Services on Android, you might have ran into an issue where you would like to:

  1. Use a concrete implementation of a Service (from a built-in or third party library).
  2. 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:

  1. We will have to be very careful to pass the correct lifecycle calls to the member LifecycleService.
  2. Other components are not aware that our Service is now lifecycle-aware.
  3. I have not seen this pattern mentioned anywhere, so this approach is not well documented and is most likely anti-pattern.
  4. 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:

  1. How to implement LifecycleOwner in a Service.

Tools Required

  1. Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 2.

Prerequisite Knowledge

  1. Intermediate Android.
  2. Kotlin.
  3. Android Services.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. 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'
  3. 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
     }
  4. 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:

  1. getLifecycle()
  2. onServicePreSuperOnBind()
  3. onServicePreSuperOnCreate()
  4. onServicePreSuperOnDestroy()
  5. 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:

  1. getLifecycle() (from LifecycleOwner interface, not Service).
  2. onBind().
  3. onCreate().
  4. onStart() (Deprecated on newer Android versions).
  5. onStartCommand().
  6. 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.

  1. Override onBind().

     override fun onBind(p0: Intent?): IBinder? {
        mServiceLifecycleDispatcher.onServicePreSuperOnBind()
        return super.onBind(p0)
     }
  2. Override onCreate().

     override fun onCreate() {
        mServiceLifecycleDispatcher.onServicePreSuperOnCreate()
        super.onCreate()
     }
  3. 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)
     }
  4. Override onStartCommand().

     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        mServiceLifecycleDispatcher.onServicePreSuperOnStart()
        return super.onStartCommand(intent, flags, startId)
     }
  5. 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

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.