Reputation: 1179
I would like to implement a tracking app which requests the current user position every 3 minutes. The app should run in background (also when the app is closed). Currently I am trying to use a WorkManager for it. Unfortunately I do not get the GPS position (Toast Message) when the app is closed.
My code:
public class LocationWorker extends Worker {
private FusedLocationProviderClient client;
public LocationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
requestLocationUpdates();
return null;
}
private void requestLocationUpdates() {
LocationRequest request = new LocationRequest();
request.setInterval(5 * 1000);
request.setFastestInterval(5 * 1000);
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
client = LocationServices.getFusedLocationProviderClient(getApplicationContext());
int permission = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION);
if (permission == PackageManager.PERMISSION_GRANTED) {
final LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Toast.makeText(getApplicationContext(),"TEST",Toast.LENGTH_LONG).show();
Location location = locationResult.getLastLocation();
if (location != null) {
Log.e("LONG", "location update " + location.getLongitude());
}
}
};
client.requestLocationUpdates(request, locationCallback,Looper.getMainLooper());
}
}
Any idea what I should do to receive the location updates in background when the app is closed? And should I use WorkManager or is something else a better solution? I also tried the PeriodicWorkRequest but it had a minimum interval (15 min).
Upvotes: 1
Views: 4116
Reputation: 21
I've faced same problem. I've solved it by using CoroutineWorker and registering BroadcastReceiver runtime. Here is my solution, I hope it will help.
class LocationUploadWorker(
private val context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
companion object {
private const val ACTION = "myapp.location"
private const val TIMEOUT = 60_000L // 1 min
private const val REQUEST = 1000
}
override suspend fun doWork(): Result {
val location = obtain() ?: return Result.retry()
val result = upload(location)
return when(result) {
SuccessResponse -> Result.success()
ErrorResponse -> Result.failure()
}
}
@RequiresPermission(allOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
private suspend fun obtain(): Location? =
// coroutines which will cancel all child coroutines if time out reached
withTimeoutOrNull(TIMEOUT) {
// this block will suspend until continuation won't be invoked
suspendCancellableCoroutine { continuation ->
// location client and request
val client = LocationServices.getFusedLocationProviderClient(context)
val request = LocationRequest().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY }
// init intent and receiver
val intent = PendingIntent.getBroadcast(context, REQUEST, Intent(ACTION), 0)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, data: Intent) {
val location = LocationResult.extractResult(data)?.lastLocation
// stop listening
client.removeLocationUpdates(intent)
context.unregisterReceiver(this)
// resume suspended continuation which basically means return location as function
continuation.resume(location)
}
}
// start listening
context.registerReceiver(receiver, IntentFilter(ACTION))
client.requestLocationUpdates(request, intent)
// stop listening if cancelled by timeout or other reason
continuation.invokeOnCancellation {
client.removeLocationUpdates(intent)
context.unregisterReceiver(receiver)
}
}
}
private suspend fun upload(location: Location) {
// upload your location here
}
}
fun Context.startUploadingLocation() {
if (checkLocationPermissions().not()) return
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = PeriodicWorkRequestBuilder<LocationUploadWorker>(15, TimeUnit.MINUTES)
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES)
.addTag(LocationWorkTag)
.build()
WorkManager
.getInstance(this)
.enqueue(request)
}
fun Context.checkLocationPermissions() =
ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
Upvotes: 2
Reputation: 478
I am trying to figure out a solution to this problem myself, and so far I discovered that in your case there are a couple of things happening.
First, since API 26, work that happens while your app is in background (or closed for that matter) is throttled down. See here -> Android Background Limits and also here -> Android Background Location Limits for location in particular.
This means that no matter what, the intervals you added in your solution won't be respected.
The second thing that I believe is happening is that FusedLocationProviderClient.requestLocationUpdates(request, locationCallback, looper) is meant to be used in foreground mode as per documentation. For background mode, it is recommended to use FusedLocationProviderClient.requestLocationUpdates(request, pendingIntent).
And this is where my current understanding of using FusedLocationProviderClient (the recommended way of retrieving location) together with WorkManager (the recommended way to executing work in the background) breaks. Because WorkManager is not intended to interoperate with Intents.
Upvotes: 0