Know Your Hardware: Mastering GPS for Live Location Tracking and Beyond
Our Android devices are packed with powerful hardware, constantly feeding us with valuable information. Learn about GPS (Global Positioning System), which can pinpoint your live location, track your direction and speed, and even measure your altitude.
(Getting a deep dive into GPS hardware fascinated me and made me want to explore various hardware in mobile devices, so to motivate me, I'm starting this "Know Your Hardware" series where I'll be posting about all the hardware in Android and documenting it in an open source app (https://github.com/MadFlasheroo7/Know-Your-Hardware) and blog about it here. The repo is open for contributions! 😄
Our Android devices are packed with powerful hardware, constantly feeding us with valuable information. It's time that we tap into their full potential and that's what this series will be focused on. Starting with GPS (Global Positioning System). It can pinpoint your live location, track your direction and speed, and even measure your altitude. So, let's start from the basics.
What is Location Manager?
Location Manager is a platform API that provides access to the system location services (but the recommended way is to use the fused provider provided by Google Play services). This lets us know the GNSS hardware capabilities and provides info about the available location providers also helps us fetch the device's live location. But what is this GNSS?
GNSS and NMEA
Both GNSS and NMEA are used to listen to location updates. NMEA(National Marine Electronics Association) existed way before GNSS(Global Navigation Satellite System) and thus can be used to fetch location on older Android versions, GNSS on the other hand helps us receive signals from satellites like GPS (US), GLONASS (Russia), Galileo (Europe), BeiDou (China) and more, this helps us determine accurate location.
Now that we have enough theory – let's start implementing it and we will look at the GNSS and location capabilities as we code along.
Implementation
To effectively monitor GPS status changes throughout your app's lifecycle, we'll create a broadcast receiver that listens for location mode updates. While Jetpack Compose offers options like DisposableEffect()
, we'll prioritize a solution that remains active as long as the app runs. This approach is particularly beneficial when considering potential code sharing across multiple screens in a multi-module project. Although there might be more optimal solutions, here's a functional (though not the most efficient) method to achieve this:
class GpsManager {
...
private val gpsReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action
if (action == LocationManager.PROVIDERS_CHANGED_ACTION) {
...
}
}
}
fun register(context: Context) {
val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
context.registerReceiver(gpsReceiver, filter)
....
}
fun unregister(context: Context) {
context.unregisterReceiver(gpsReceiver)
}
}
Here we create a GpsManager()
class that is responsible for registering and unregistering the broadcast receiver. Here, first, we create a broadcast receiver object function that registers or unregisters the receiver which we call it in onCreate()
and onDestroy()
respectively, but here a problem arises that our GPS manager does not know the initial GPS state until the mode is changed. So to solve that, we will create a new private class that provides us the initial GPS status.
private class InitialGpsStatus(context: Context) {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
...
}
Now that we have created our classes we have to register it in the onCreate()
method.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gpsManager.register(this)
...
}
Now we can simply pass our instance of gpsManager
to the nav host which can be shared to all the screens that need gps state. Now let's see what LocationManager
provides to us.
Location Manager
First, we create an instance of LocationManager
.
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
Now to help us fetch location android devices have 4 location providers Fused, GPS, Network, and Passive.
Fused
- Fused location provider combines inputs from all the providers to provide optimal accuracy and battery consumption.GPS
- GPS (Global Positioning System) uses the dedicated hardware in the device to communicate with the satellite to provide the location. Works well in open sky and battery consumption is high.Network
- Uses WIFI or Cell towers to find out estimated location battery consumption is pretty low but at the cost of accuracy.Passive
- Passive provider don't actively request location updates but rather rely on other providers to give the location updates. Consumes little to no battery since it doesn't actively request location updates
LocationManager()
and Location()
provides a lot of useful methods that we cannot cover in a single blog, but here, treat your eyes with this waterfall of information!
Fused Client VS Location Manager
Before learning how to fetch location it's important to decide which client to use to fetch location. So, what is fused client and location manager client anyway?
Location manager is a platform API provided by the underlying OS and Fused on the other hand is provided by Google Play services now both have their pros and cons. Since location manager is provided by the platform we can ensure that irrespective of whether the device supports Google Play services or not it will work as intended, but let's say your device supports both fused and location manager then which one should you use? Fused client is faster in providing you the response whereas the location manager is more accurate most of the time. So depending on your use case, you can decide which one to use but if you want the most accurate location fix its best to have both fused and location manager then pick whichever is more accurate.
Note: either one of them is best when you set minium of 3 seconds interval to fetch live location for more consistant accuracy.
Track Live Location
Now finally to the juicy stuff how do we track live location? most importantly how do we do it efficiently? When working with location data in Android, you have several options at your disposal. Each method, such as getCurrentLocation(), getLastKnownLocation(), and requestLocationUpdates(), serves a unique purpose depending on your app's needs.
getLastKnownLocation()
- Returns the last cached location by the specified provider, useful in a use case where getting an accurate and latest location is not a priority.getCurrentLocation()
- Returns the latest location fix at the time this getting called, usefull when you want a single best location estimate.requestLocationUpdates()
- Returns the latest location on either a specifiedLooper
orExecutor
, useful when you want to track live location of the device.
So, for our use case, we will use requestLocationUpdates()
. The requestLocationUpdates()
method offers flexibility in how you receive location updates. You can choose a callback mechanism either the legacy LocationListener
or the more modern LocationCallback
. Additionally, you can specify how and where these callbacks are executed using either a Looper
or an Executor
.
LocationListener
- location listener is a legacy way to fetch location, it returns a location fix every time its requested.LocationCallback
- location callback is more modern way to fetch location, it rather keeps providing location updates untill stoped.Looper
- a looper runs the task that a thread will run and execute them in a queue one after the other, its tied to a specfied thread.Executor
- a executor runs on the specfied thread and it manages the scheduling and manages the thread pool internally. Executors are more flexible.
To keep this example simple we will use Looper
.
@Composable
fun FusedLocationUpdatesListener(
locationClient: FusedLocationProviderClient,
locationRequest: LocationRequest,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onUpdate: (result: LocationResult) -> Unit,
) {
val context = LocalContext.current
val currentOnUpdate by rememberUpdatedState(newValue = onUpdate)
// Whenever on of these parameters changes, dispose and restart the effect.
DisposableEffect(locationRequest, lifecycleOwner) {
val locationCallback: LocationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
currentOnUpdate(result)
}
}
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
locationClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper(),
)
} else if (event == Lifecycle.Event.ON_STOP) {
locationClient.removeLocationUpdates(locationCallback)
}
}
// Add the observer to the lifecycle
lifecycleOwner.lifecycle.addObserver(observer)
// When the effect leaves the Composition, remove the observer
onDispose {
locationClient.removeLocationUpdates(locationCallback)
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
This sample creates a disposable effect that is executed every time the parameters change and disposes and restarts the effect. Here we create a lifecycle observer that requests location updates on onStart()
and removes location updates on onStop()
, and the disposable effect is used to remove both the observer and location client from memory when the effect leaves the composition.
Conclusion
GPS and location in Android are way too complex to cover their workings in this blog so you can check out the repo here, the app not only lists the details of the GNSS hardware but also lets you switch the providers between LocationManager
and FusedClient
on the fly and look at their capabilities. This app and repo will keep getting updated as I learn more not just about the GPS but also more hardware available in Android devices. If you find any work that could be improved check the repo and contribute and that's it for now, do keep an eye on the repo for more hardware coverage until then
Happy Composing! :)
Bonus Links
Repo link:
Fix nested layouts in compose:
Android lifecycle from the view of compose:
Publish your library to Maven Central: