Search code examples
androidkotlinserviceviewmodel

Maintaining State in foreground service while App is closed


I'm working on a project that interacts with a BLE device. When the app is open, I actively display data transmitted from the device. However, I want to be able to receive data when the app is closed as well. Right now, I have a foreground service running that continues to receive data from the BLE device after the app is closed. I want to persist this data and update the state when the app is resumed.

Right now, I have a ViewModel that handles the UI of the fragments. My plan was initially to pass all my data through bundles to my Service when my MainActivity is destroyed. And then, whenever I receive data from the BLE device, I store the new states in shared preferences. When MainActivity resumes, I check sharedPreferences to retrieve the state updated in the Service and we continue from there.

However, I'm not sure if this is the best way to do things, because I have quite a bit of data stored in my ViewModel (2 dataclasses, 1 List of BLE Devices, and 1 List of a BLE Service). I was wondering if there's a better way to maintain states like this, like for example a ViewModel but more server-like.


Solution

  • Take a look. I've created a foreground service and tested this code on API 34.

    There is no need to use Bundles or Binders.

    I removed the irrelevant lines. As you can see I can transfer data from MainActivity to FGService and vice versa by simply calling methods.

    Normally both the service and the activity are running only once at a time. So using a singleton is just fine. If you plan to run the activity multiple times simultaneously you have to update the code.

    To persist the data I recommend using SQLite because it's not only faster than shared prefs.

    MainActivity.kt:

    package com.example.myapplication
    
    import android.content.Intent
    import android.util.Log
    import androidx.appcompat.app.AppCompatActivity
    
    class MainActivity : AppCompatActivity() {
    
        companion object {
            var sngltn: MainActivity? = null
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            sngltn = this
            startForegroundService(Intent(this, FGService::class.java))
        }
    
        fun fromFGService(data: Int) {
            Log.e("moh", "MainActivity received some data from FGService: $data")
        }
    
        override fun onDestroy() {
            super.onDestroy()
            if (FGService.sngltn != null)
                FGService.sngltn!!.fromMainActivity(123)
            sngltn = null
        }
    }
    

    FGService.kt:

    package com.example.myapplication
    
    import android.app.Service
    import android.os.Looper
    import android.util.Log
    
    class FGService : Service() {
    
        companion object {
            var sngltn: FGService? = null
        }
    
        override fun onCreate() {
            super.onCreate()
            sngltn = this
            if (MainActivity.sngltn != null) MainActivity.sngltn!!.fromFGService(456)
            if (Looper.getMainLooper() == Looper.myLooper())
                Log.e("moh", "FGService running on main thread (good)")
            else
                Log.e("moh", "FGService not running on main thread (bad, not as expected)")
        }
    
        fun fromMainActivity(data: Int) {
            Log.e("moh", "FGService received some data from MainActivity: $data")
        }
    
        override fun onDestroy() {
            super.onDestroy()
            sngltn = null
        }
    }