Search code examples
androidkotlinviewmodelforeground-service

Trouble Updating RecyclerView with Data from Foreground Location Service in Kotlin


Problem: I'm developing a simple test app in Kotlin with a foreground location service that updates GPS coordinates (latitude and longitude) every 5 seconds. The app has one main activity and 3 fragments, connected via basic navigation. In the third fragment, there's a RecyclerView that should receive and append new location updates. I succeeded in adding items to the list via a hardcoded button, but I’m lost on how to post the new location updates automatically from the service to the fragment.

Code Snippet: In my LocationService, I'm updating the notification with the new location but struggling to send the updated location to the fragment. Here's the relevant code:

private var locationViewModel = SampleViewModel()
   override fun onCreate() {
        super.onCreate()
        locationClient = DefaultLocationClient(
            this,
            LocationServices.getFusedLocationProviderClient(applicationContext)
        )
        locationViewModel = ViewModelProvider(applicationContext)[SampleViewModel::class.java]
        //Type mismatch.
            Required:
              ViewModelStoreOwner
               Found:
              Context!

    }


// In LocationService
private fun updateLocationList(lat: String, lon: String) {
    val event = TripEvent(lat, lon)
    // Tried using ViewModel here, but can't access application context
    //locationViewModel.addTripEvent(event)
}

Question: How can I effectively pass the location updates from my foreground service to the fragment so that the RecyclerView automatically updates with new data?

What I've Tried: I attempted to use a shared ViewModel (SampleViewModel) to update the list, but ran into issues trying to declare and access the ViewModel in the service. The main challenge is that ViewModelProvider doesn’t work in the service since it lacks access to the application context.


Solution

  • 1. Use LocalBroadcastManager with a BroadcastReceiver

    //In LocationService
    import androidx.localbroadcastmanager.content.LocalBroadcastManager
    import android.content.Intent
    
    private fun updateLocationList(lat: String, lon: String) {
        val intent = Intent("LOCATION_UPDATE")
        intent.putExtra("latitude", lat)
        intent.putExtra("longitude", lon)
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
    }
    
    // In your fragment
    import android.content.BroadcastReceiver
    import android.content.Context
    import android.content.Intent
    import android.content.IntentFilter
    import androidx.localbroadcastmanager.content.LocalBroadcastManager
    
    private val locationUpdateReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            val lat = intent?.getStringExtra("latitude")
            val lon = intent?.getStringExtra("longitude")
            lat?.let {
                lon?.let {
                    // Update your RecyclerView adapter here
                    val event = TripEvent(lat, lon)
                    // Assuming you have a function in your adapter to add items
                    myAdapter.addTripEvent(event)
                }
            }
        }
    }
    
    override fun onStart() {
        super.onStart()
        LocalBroadcastManager.getInstance(requireContext())
            .registerReceiver(locationUpdateReceiver, IntentFilter("LOCATION_UPDATE"))
    }
    
    override fun onStop() {
        super.onStop()
        LocalBroadcastManager.getInstance(requireContext())
            .unregisterReceiver(locationUpdateReceiver)
    }
    



    2. If you prefer to stick with ViewModel you can use a Shared ViewModel with Service Binding

    // In LocationService
    import android.os.Binder
    
    class LocationService : Service() {
        private val binder = LocalBinder()
        
        inner class LocalBinder : Binder() {
            fun getService(): LocationService = this@LocationService
        }
    
        override fun onBind(intent: Intent?): IBinder {
            return binder
        }
    
        private fun updateLocationList(lat: String, lon: String) {
            val event = TripEvent(lat, lon)
            locationViewModel.addTripEvent(event)  // You can use the ViewModel now
        }
    }
    
    private lateinit var locationService: LocationService
    private var bound = false
    
    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            val binder = service as LocationService.LocalBinder
            locationService = binder.getService()
            bound = true
        }
    
        override fun onServiceDisconnected(arg0: ComponentName) {
            bound = false
        }
    }
    
    override fun onStart() {
        super.onStart()
        Intent(this, LocationService::class.java).also { intent ->
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
        }
    }
    
    override fun onStop() {
        super.onStop()
        unbindService(serviceConnection)
        bound = false
    }