Kahibaro
Discord Login Register

27.2 Using GPS

Understanding GPS on Android

Global Positioning System, or GPS, is one of several location providers on Android. GPS uses satellites to determine the device position, which is usually accurate but can be slower to get a fix and may not work well indoors. In contrast, other providers can use Wi‑Fi, cell towers, or Bluetooth. In this chapter, the focus is on how to work specifically with GPS on Android and how to prefer GPS as the location source, without re‑explaining general location concepts from the parent chapter.

Android exposes GPS through the location APIs. Historically, you would work directly with LocationManager and request updates from the GPS_PROVIDER. Modern apps often use the Fused Location Provider from Google Play services, which can still prioritize GPS but also combine it with other signals. In both cases, you must respect user privacy, request the correct permissions, and handle cases where GPS is disabled or not available.

When working with GPS you must always:

  1. Request the appropriate location permissions at runtime.
  2. Check whether location providers such as GPS are enabled.
  3. Stop receiving updates when they are no longer needed, to save battery.

Required Permissions for GPS

To use GPS you need location permissions. At minimum, you must declare them in AndroidManifest.xml. For GPS based location you typically use precise location rather than coarse.

A typical manifest declaration for precise foreground location looks like this:

<manifest ...>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

ACCESS_FINE_LOCATION allows the app to use precise location, which includes GPS. If your app only has ACCESS_COARSE_LOCATION, the system might provide approximate location that can come from cell or Wi‑Fi signals and may not use GPS at all.

On modern Android versions, declaring permissions in the manifest is not enough. You must also request them at runtime. The runtime request is done from an Activity or Fragment and involves checking if the permission is already granted and, if not, asking the user.

A minimal example in Kotlin using ActivityCompat looks like this:

private val LOCATION_PERMISSION_REQUEST = 100
private fun ensureLocationPermission() {
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            LOCATION_PERMISSION_REQUEST
        )
    } else {
        startGpsUpdates()
    }
}
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == LOCATION_PERMISSION_REQUEST &&
        grantResults.isNotEmpty() &&
        grantResults[0] == PackageManager.PERMISSION_GRANTED
    ) {
        startGpsUpdates()
    }
}

Here startGpsUpdates() is a function that will actually begin listening to the GPS provider. This separation is useful so that you can call the same function whether the permission was already granted or just now approved.

If your app also needs to receive GPS updates when running in the background, additional background location permission is required, but the details about background usage belong in broader security and permissions discussions.

Checking if GPS Is Enabled

Even if the user has granted location permission, GPS itself can be turned off at the system level. Attempting to listen to GPS updates while the GPS provider is disabled will not give you useful results.

Using the platform LocationManager, you can check provider status:

private fun isGpsEnabled(): Boolean {
    val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
    return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}

If isGpsEnabled() returns false, you may want to prompt the user to turn it on. You cannot enable GPS directly from your app. Instead, you launch the system location settings screen:

private fun openLocationSettings() {
    val intent = Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)
    startActivity(intent)
}

A typical user flow is to check permissions first, then check if GPS is enabled, and if not, guide the user to settings.

Using GPS with LocationManager

The platform LocationManager provides direct access to GPS. When you request updates from the GPS provider, you specify how often you want updates and how far the device must move before a new update is delivered. This gives you fine grained control but also puts more responsibility on you to manage battery and accuracy.

To get a basic GPS fix using LocationManager, you usually:

  1. Obtain the LocationManager instance from the system.
  2. Create a LocationListener to receive location updates.
  3. Request updates from LocationManager.GPS_PROVIDER.
  4. Remove updates when no longer needed.

The following example shows a simple Activity that receives GPS updates:

class GpsActivity : AppCompatActivity() {
    private lateinit var locationManager: LocationManager
    private lateinit var locationListener: LocationListener
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_gps)
        locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
        locationListener = object : LocationListener {
            override fun onLocationChanged(location: Location) {
                val latitude = location.latitude
                val longitude = location.longitude
                val accuracy = location.accuracy
                // Update UI with GPS coordinates
            }
            override fun onProviderEnabled(provider: String) {
                // GPS was enabled
            }
            override fun onProviderDisabled(provider: String) {
                // GPS was disabled
            }
        }
        ensureLocationPermission()
    }
    private fun startGpsUpdates() {
        if (!isGpsEnabled()) {
            openLocationSettings()
            return
        }
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        locationManager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER,
            2000L,
            5f,
            locationListener
        )
    }
    override fun onPause() {
        super.onPause()
        stopGpsUpdates()
    }
    private fun stopGpsUpdates() {
        locationManager.removeUpdates(locationListener)
    }
}

requestLocationUpdates takes the provider name, a minimum time between updates in milliseconds, a minimum distance in meters, and the listener. Setting a shorter time or smaller distance makes GPS more active and precise but also drains more battery.

onPause calls stopGpsUpdates() to avoid keeping GPS active when the activity is not visible. This is an important pattern when using GPS directly.

Always remove GPS location updates in your lifecycle methods, for example in onPause or onStop. Forgetting to remove updates can keep GPS active, drain the battery quickly, and can cause your app to be restricted by the system.

Getting the Last Known GPS Location

Sometimes you only need a single GPS location, for example to prefill a field with the current coordinates. In such cases, you can check the last known location from the GPS provider. This value is cached by the system and may not be fully up to date, but it is fast and avoids waiting for a fresh fix.

Using LocationManager, you can get it like this:

@SuppressLint("MissingPermission")
private fun getLastGpsLocation(): Location? {
    val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
    return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
}

The @SuppressLint("MissingPermission") annotation is used because the method would normally require a permission check. You must ensure that location permission has been granted before calling this function.

You can use this last known location to quickly update the UI and then optionally start fresh GPS updates for a more accurate position.

Prioritizing GPS with Fused Location Provider

Many modern apps use the Fused Location Provider from Google Play services because it simplifies location handling. It can combine GPS, Wi‑Fi, and cell networks. If you still want to emphasize GPS, you can configure a LocationRequest that requests high accuracy, which typically activates GPS when available.

To use the Fused Location Provider, you add the proper dependency in your Gradle file. The specifics of dependency management are covered elsewhere in the course, so here the focus is on how to configure the request to favor GPS.

A typical setup for high accuracy location looks like this:

class FusedGpsActivity : AppCompatActivity() {
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private lateinit var locationCallback: LocationCallback
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fused_gps)
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        locationRequest = LocationRequest.Builder(
            Priority.PRIORITY_HIGH_ACCURACY,
            2000L
        ).setMinUpdateDistanceMeters(5f)
            .build()
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(result: LocationResult) {
                val location = result.lastLocation ?: return
                val lat = location.latitude
                val lng = location.longitude
                val accuracy = location.accuracy
                // Update UI with GPS style high accuracy coordinates
            }
        }
        ensureLocationPermission()
    }
    private fun startHighAccuracyUpdates() {
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        fusedLocationClient.requestLocationUpdates(
            locationRequest,
            locationCallback,
            Looper.getMainLooper()
        )
    }
    private fun stopHighAccuracyUpdates() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }
    override fun onPause() {
        super.onPause()
        stopHighAccuracyUpdates()
    }
}

Here Priority.PRIORITY_HIGH_ACCURACY instructs the system to aim for the best possible accuracy, which usually means using GPS if available. The fused provider manages underlying sensors and might choose Wi‑Fi or cell signals when GPS is unavailable, but you do not have to manage providers directly.

setMinUpdateDistanceMeters(5f) and the interval of 2000L milliseconds control how often you receive updates and how far the device must move between updates.

Handling Common GPS Issues

Working with GPS is not just about code that fetches coordinates. In practice, you must handle several common problems. These include slow initial fixes, inaccurate readings, and unavailability in certain environments.

A GPS fix can take several seconds, especially when the user has just enabled GPS or moved a long distance since the last fix. During this time, you may not receive any onLocationChanged callbacks. A useful pattern is to show a small indicator on screen such as a progress bar or text like "Seeking GPS signal" until the first update arrives.

Accuracy is expressed in meters in the Location.accuracy property. A smaller value means a more precise fix. For example, an accuracy of 5 means the actual position is likely within 5 meters of the reported coordinates. You can check this value and decide whether to use the reading. You might, for example, ignore locations with accuracy larger than a certain threshold.

You should also deal gracefully with GPS unavailability. Indoors or in dense urban areas, the signal may be weak. Even in high accuracy mode, the fused provider might fall back to Wi‑Fi and provide a location with larger accuracy values. Your UI should communicate that the location is approximate if needed, rather than pretending to be exact.

To avoid blocking the user, do not perform any long running work on the main thread while waiting for GPS. The location callbacks are asynchronous, so you can keep your UI responsive while the system looks for satellites.

Never assume that GPS will always be available or quick. Always:

  1. Time out operations that depend on GPS.
  2. Check location.accuracy before using coordinates.
  3. Inform the user when only approximate location is available.

Converting GPS Coordinates and Simple Distance

When you use GPS you receive latitude and longitude in decimal degrees. In some apps you might display them directly, for example 37.4219983, -122.084. In others, you might convert them to a more readable format, but that formatting is not specific to Android.

Sometimes you want to know how far apart two GPS points are. The Android Location class has a distanceTo method that computes the distance between two Location objects in meters. Internally, this uses a formula related to spherical geometry. You can use it directly, without implementing the formula yourself.

For example:

fun distanceBetween(loc1: Location, loc2: Location): Float {
    return loc1.distanceTo(loc2)
}

If you ever need to implement a simple distance calculation by hand, a common choice is the Haversine formula. For two points with latitude and longitude in radians, the formula for distance on a sphere of radius $R$ is:

$$
d = 2R \arcsin \left(
\sqrt{
\sin^2\left(\frac{\Delta \varphi}{2}\right) +
\cos(\varphi_1) \cos(\varphi_2) \sin^2\left(\frac{\Delta \lambda}{2}\right)
}
\right)
$$

Here $\varphi_1$ and $\varphi_2$ are latitudes, $\lambda_1$ and $\lambda_2$ are longitudes, and $\Delta \varphi$ and $\Delta \lambda$ are their differences.

For most Android apps, using distanceTo on Location objects is enough and avoids dealing directly with trigonometry.

Battery and Performance Considerations

GPS is one of the most power hungry sensors. Continuous high frequency GPS updates can drain a large portion of the battery in a short time. For this reason, you must design your app so that it only uses GPS when it provides clear value to the user and only for as long as necessary.

Use larger update intervals and distance thresholds when you do not need very frequent updates. For example, if your app only needs to know whether the user has changed city, you can request updates every few minutes and with a larger minimum distance. For navigation style apps, more frequent updates are reasonable, but you should still pause them when the user leaves the navigation screen or locks the device.

Also remember that GPS does not work well without a clear view of the sky. Constantly requesting GPS updates in a basement will only waste power and still give poor results. You can monitor accuracy values and decide to reduce request frequency in environments where GPS cannot get a good fix.

Careful management of GPS usage is a key sign of a well designed Android app that respects the user device resources.

Summary

Using GPS on Android involves combining precise permissions, provider configuration, lifecycle management, and user feedback. You declare ACCESS_FINE_LOCATION, request it at runtime, check whether GPS is enabled, and then use either LocationManager with GPS_PROVIDER or the Fused Location Provider in high accuracy mode. You manage updates through the activity lifecycle to avoid wasting battery, handle slow or unavailable GPS fixes gracefully, and interpret accuracy and distance correctly when using coordinates.

All other aspects of working with location such as background access, geofencing, and advanced privacy controls build on these GPS basics.

Views: 1

Comments

Please login to add a comment.

Don't have an account? Register now!