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.
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
}