Skip to main content

12 - Fused location

Fused location

Simple, battery-efficient location API for Android. The fused location provider is a location API in Google Play services that intelligently combines different signals to provide the location information. It manages the underlying location technology and provides a simple API so that you can specify requirements at a high level, like high accuracy or low power. It also optimizes the device's use of battery power.

Add needed dependency to build.gradle.kts

dependencies {
// https://mvnrepository.com/artifact/com.google.android.gms/play-services-location
implementation("com.google.android.gms:play-services-location:21.3.0")
private lateinit var fusedLocationClient: FusedLocationProviderClient

override fun onCreate(savedInstanceState: Bundle?) {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
}

Last known and current location

fusedLocationClient.lastLocation
.addOnSuccessListener { location : Location ->
}

Returns immediate result. Location might be old.

fusedLocationClient.getCurrentLocation
.addOnSuccessListener { location : Location ->
}

Gets a fresher, more accurate location more consistently. However, this method can cause active location computation to occur on the device.

Location request settings

    private val locationRequest = LocationRequest
.Builder(2000) // interval
.setPriority(Priority.PRIORITY_HIGH_ACCURACY)
.setMinUpdateIntervalMillis(1000)
.setMinUpdateDistanceMeters(1f)
.build()
  • setMinUpdateIntervalMillis - fastest rate in millis. Defaults to same as interval. If set to lower, you might get faster updates.
  • setMinUpdateDistanceMeters - distance at least X meters
  • setPriority - hint for source selection
    • PRIORITY_HIGH_ACCURACY - most likely gps. High power.
    • PRIORITY_BALANCED_POWER_ACCURACY - coarse, ca 100m precision.
    • PRIORITY_LOW_POWER - coarse, 10km precision. Low power.
    • PRIORITY_NO_POWER - if there is a location - you will get it. No power usahge trigered by app.

So PRIORITY_HIGH_ACCURACY with ACCESS_FINE_LOCATION permission and update interval of 5000-1000 millis - ca 1m precision. Maping apps with real time location.

Periodic location updates

Use requestLocationUpdates(). Might use lots of power.

private lateinit var locationCallback: LocationCallback

override fun onCreate(savedInstanceState: Bundle?) {
// ...

locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
for (location in locationResult.locations){
// Update UI with location data
}
}
}
}


private fun startLocationUpdates() {
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}

private fun stopLocationUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback)
}

Full service code

package ee.taltech.permissionsdemo

import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.location.Location
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.ServiceCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority

class ForegroundService : Service() {
companion object {
private val TAG = this::class.java.declaringClass!!.simpleName
const val NOTIFICATION_ID = 100
}

private var prevLocation: Location? = null
private lateinit var fusedLocationClient: FusedLocationProviderClient
private var distance = 0.0

private val locationRequest = LocationRequest
.Builder(2000)
.setPriority(Priority.PRIORITY_HIGH_ACCURACY)
.setMinUpdateIntervalMillis(1000)
.setMinUpdateDistanceMeters(1f)
.build()

private var locationCallback: LocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
locationResult.locations.forEachIndexed { idx, location ->
locationReceive(location, idx == locationResult.locations.count() - 1)
}
}
}

override fun onCreate() {
super.onCreate()
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
}

@SuppressLint("MissingPermission")
private fun getLastLocation() {
Log.d(TAG, "Starting initial location receive")
fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
if (location != null) {
locationReceive(location)
} else {
Log.w(TAG, "Initial location was null")
}
}
}

@SuppressLint("MissingPermission")
private fun locationReceive(location: Location, updateNotification: Boolean = false) {
prevLocation?.apply {
distance += location.distanceTo(this)
}

if (updateNotification) {
val notification = buildNotification(this, "Foreground Service", "Distance: $distance")
NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification)
}

prevLocation = location
}

@SuppressLint("MissingPermission")
private fun requestLocationUpdates() {
if (!permissionsGranted(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.POST_NOTIFICATIONS
)
) return

getLastLocation()

fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.myLooper()
)
}

private fun permissionsGranted(vararg permissions: String): Boolean {
permissions.forEach { permission ->
if (ActivityCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED
) {
return false
}
}
return true
}

override fun onBind(intent: Intent?): IBinder? {
return null
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startFgService()
requestLocationUpdates()
return START_STICKY
}

private fun startFgService() {
ServiceCompat.startForeground(
this,
NOTIFICATION_ID, //
buildNotification(this, "Foreground Service", "Foreground Service running"),
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
}

private fun buildNotification(context: Context, title: String, content: String): Notification {
val notificationIntent = Intent(
context,
MainActivity::class.java
)
val pendingIntent = PendingIntent.getActivity(
context,
123,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)

return NotificationCompat.Builder(context, MainActivity.NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setSound(null)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.build()
}
}