I'm currently working on an Android app where I need to fetch the user's location in the background at regular intervals using the Periodic WorkManager. I have set up a WorkRequest to trigger every 15 minutes, and it successfully retrieves the user's location when the app is in the foreground.
However, I'm facing an issue when the app is killed or in the background. Despite having all the required location permissions granted, the location returned is null in these scenarios.
Permissions
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Interface and Implementations
interface LocationClient {
fun getLocationUpdates(context: Context, interval: Long): Flow<Location>
suspend fun getLocation(context: Context, interval: Long): Location
class LocationException(message: String) : Exception()
}
class LocationClientImpl @Inject constructor(
private val client: FusedLocationProviderClient
) : LocationClient {
@SuppressLint("MissingPermission")
override fun getLocationUpdates(context: Context, interval: Long): Flow<Location> {
return callbackFlow {
if (!context.hasLocationPermission()) {
throw LocationClient.LocationException("Missing location permission")
}
val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val isNetworkEnabled =
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if (!isGpsEnabled && !isNetworkEnabled) {
throw LocationClient.LocationException(message = "GPS is disabled")
}
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, interval)
.setWaitForAccurateLocation(false)
.build()
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
result.locations.lastOrNull()?.let { location ->
launch { send(location) }
}
}
}
client.requestLocationUpdates(
request,
locationCallback,
Looper.getMainLooper()
)
awaitClose {
client.removeLocationUpdates(locationCallback)
}
}
}
@SuppressLint("MissingPermission")
override suspend fun getLocation(context: Context, interval: Long): Location {
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, interval)
.setWaitForAccurateLocation(false)
.build()
return suspendCancellableCoroutine { continuation ->
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
result.locations.lastOrNull()?.let { location ->
client.removeLocationUpdates(this)
continuation.resume(location)
}
}
override fun onLocationAvailability(availability: LocationAvailability) {
super.onLocationAvailability(availability)
if (!availability.isLocationAvailable) {
client.removeLocationUpdates(this)
continuation.resumeWithException(LocationClient.LocationException("Location not available"))
Log.e(this::class.java.name, "onLocationAvailability: ${availability.isLocationAvailable}", )
}
}
}
client.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
}
}
}
Worker
@HiltWorker
class LocationWorker @AssistedInject constructor(
@Assisted var context: Context,
@Assisted parameters: WorkerParameters,
private val deviceRepo: DeviceRepo,
private val locationClient: LocationClient
) :
CoroutineWorker(context, parameters) {
private val TAG = "LocationWorker"
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
//deviceRepo.addDevice(context)
val result = locationClient.getLocation(context, 1000)
Log.d(TAG, "doWork: ${result.latitude}")
Result.success()
}
}
Work Request in Activity
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workManager = WorkManager.getInstance(this@MainActivity)
val request: PeriodicWorkRequest =
PeriodicWorkRequestBuilder<LocationWorker>(
5,
TimeUnit.MINUTES
).setConstraints(constraints).build()
workManager.enqueueUniquePeriodicWork(
"reminder_notification_work",
ExistingPeriodicWorkPolicy.KEEP,
request
)
workManager.getWorkInfosForUniqueWorkLiveData("reminder_notification_work")
.observe(lifecycleOwner) { workInfo ->
workInfo.forEach {
workManagerState.value = it.state.toString()
}
}