How to get current GPS location in Android

How to get current GPS location in Android

Today we are going to learn about how we can get device’s current location. This is an easy to follow tutorial. Even if you are a complete beginner you will be able to follow along and get GPS location in android. I have broken down this tutorial into parts.

However, to follow along you must already have an understanding on how to use Android Studio. I will only be explaining you the implementation part and not the project creation part and such. And this is going to be quite long tutorial per se.

Introduction

Before we begin the actual implementation. There is something you must understand about getting location from device’s GPS. Though this seems trivial but there are 2 ways on how you might implement this feature in your android application.

Why are there multiple ways of getting location ?

That is a really good question. And here I will answer that. It is because of how the Android platform evolved. Google has updated their core API and optimized them for lower battery consumption. And there are various different things that have changed under the hood. The changes such as making the API more modular with the introduction of Android X.

Google always recommends using their API because they have honed it to perfection. And they are not wrong but sometimes it just doesn’t work as expected [see below].

Also the political and or agreement differences prevail over everything else. Example being Huawei brand in china has cut off google services in their devices. The reason is purely political but you get the gist of what I’m saying here.

EasyPermissions library

I am using a library provided by Google known as Easy Permissions. It makes it easy for us to manage the permissions everything will be taken care by the library.

Though this is completely optional and you can use your own implementation. But I don’t want to get into the boilerplate hell and loose track of my main task. So there is that you can replace / handle the permissions your own way. But for the sake of this article I’m using Easy Permissions.

You can find the link to library here, click to visit.

Open up your /app > build.gradle file and add the following dependency in dependencies block.

implementation 'pub.devrel:easypermissions:3.0.0' // make sure to replace the version with the current version by visiting the link

After adding the dependency sync your project File > Sync Project With Gradle. We are all set including the library to be used with the project, more on the use case below.

Setting up prerequisites

Before we continue let us set some classes for our use cases. Look at the snippet of code below to know exactly what are we going to need.

Add the following string item in your strings.xml this is the permission rationale. Meaning it will be shown to the user if the user keeps on denying the location permission request during runtime.

<string name="permission_rationale">Permissions are absolutely necessary.</string>

Create a file with name LocationHelper.kt which we will be using later in this article. But for now create it with just these contents.

class LocationHelper() { companion object { const val REQUEST_CODE = 123 val PERMISSIONS = arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) } }

The REQUEST_CODE is for requesting permission and PERMISSIONS is the necessary permissions which we will be requesting during the runtime.

We need to create another interface with name LocationHelperContract which we will be utilizing to switch between two different ways of collecting location.

interface LocationHelperContract { fun startGettingLocationUpdates() fun stopGettingLocationUpdates() }

These method will be overridden later to write a custom implementation. We will be looking at these in brief later on this article.

You don’t really need this if you’re just using one or the other implementation but leaving it there doesn’t really do any harm either.

Defining the update callback

This is nothing but the callback interface which we will be implementing to get back the location updates from our other class in our activity class. It is just an interface with to methods as defined below.

Create a LocationUpdateCallback class in your project and add the following in the file.

interface LocationUpdateCallback { fun onLocationUpdate(location: Location) fun onNoLocation() }

That’s it we will be using this later to get the updated location in our activity class.

Turning on GPS with code

Next we are going to show a popup to the user to turn on GPS. Though the method use might be marked as deprecated. It is the only way currently know to ask user to turn on GPS at from within app.

The code below contains necessary code for showing the GPS popup if GPS is not turned on. Create a file called LocationUtil.kt with the following contents

object LocationUtil { /** * Check if gps is turned on. * * @param context the context of the application * @return true if turned on else false */ private fun isGpsTurnedOn(context: Context): Boolean { val manager = context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager return manager.isProviderEnabled(LocationManager.GPS_PROVIDER) } fun turnOnGpsIfNot(context: Activity) { if (isGpsTurnedOn(context)) return displayLocationSettingsRequest(context) } /** * Turn on GPS request is shown by this method. * * @param context */ private fun displayLocationSettingsRequest(context: Activity) { // GPS Already active, no further action required if (isGpsTurnedOn(context)) return val googleApiClient = GoogleApiClient.Builder(context) .addApi(LocationServices.API).build() googleApiClient.connect() val locationRequest = LocationRequest.create() locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY locationRequest.interval = 5000 locationRequest.fastestInterval = (1000 / 2).toLong() val builder: LocationSettingsRequest.Builder builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest) builder.setAlwaysShow(true) val result: PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi.checkLocationSettings( googleApiClient, builder.build() ) result.setResultCallback { result1 -> val status = result1.status when (status.statusCode) { LocationSettingsStatusCodes.SUCCESS -> { } LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> // LocationModel settings are not satisfied try { // Show the dialog by calling startResolutionForResult(), and check the result // in onActivityResult(). status.startResolutionForResult(context, 10) } catch (e: IntentSender.SendIntentException) { // unable to execute the request } LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { }// no permission granted }// all settings are satisfied } } }

Now after that we are going to call this code in MainActivity as below.

class MainActivity : AppCompatActivity() { // your regular code override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // request to turn GPS on LocationUtil.turnOnGpsIfNot(this) } // your regular code }

In the code above we are requesting the user to turn GPS on. The util object will help us with that LocationUtil.turnOnGpsIfNot(this).

Now everything is setup let us move along with requesting the permission.

Requesting location permissions

The first thing we must do before actually getting location updates is: request permissions from user. Because from API 23 and above we have to explicitly ask user for permissions to use specific features of the device.

Let us look at how we might accomplish it with Easy Permissions library.

First include the permissions below in the AndroidManifest.xml within your project. We will need these two permissions for accessing the GPS location updates.

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

Here we are going to request the necessary permission in MainActivity‘s onCreate() { } shown in the snippet below.

Inspect and follow along with the code below and you’re all set.

class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { private fun initSequence() { } private fun hasRequiredPermissions(): Boolean { return EasyPermissions.hasPermissions(this, *LocationHelper.PERMISSIONS) } private fun requestRequiredPermissions() { EasyPermissions.requestPermissions( this, getString(R.string.permission_rationale), LocationHelper.REQUEST_CODE, *LocationHelper.PERMISSIONS ) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) LocationUtil.turnOnGpsIfNot(this) requestPermissionIfNotAndContinue() } @AfterPermissionGranted(LocationHelper.REQUEST_CODE) fun requestPermissionIfNotAndContinue() { if (hasRequiredPermissions()) { // we have permission do things initSequence() } else { // request permission requestRequiredPermissions() } } override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) { // continually ask for permission until it has been granted requestPermissionIfNotAndContinue() } override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) { // permission has been granted invoke the method again requestPermissionIfNotAndContinue() } }

The method names are pretty straight forward to understand. You might get confused with the annotation @AfterPermissionGranted(LocationHelper.REQUEST_CODE). But this is just annotation which is from the the Easy Permissions library.

The class must implement the EasyPermissions.PermissionCallbacks for getting callbacks from permission request. Then we will have to override the onPermissionsDenied() and onPermissionsGranted() methods.

And once the permissions are granted the code inside the initSequence() method will be executed.

Let us look at how to get location in different ways below with the actual source code.

We will be using FusedLocationProviderClient to implement location.

Google suggest using their higher(ish) level API for requesting location updates. This is because they have put much engineering into creating an abstraction layer on top of the system’s API to get location update.

PROS:
– Has lower impact on battery
– Less boilerplate code to implement
– Comparatively easy to implement

CONS:
– Could sometimes be inaccurate
– Doesn’t work on devices which doesn’t have Google Location Libraries installed

I do prefer Google’s way of handling location. Because it is really very simple and easy. Despite the fact it is simple it provides accurate locations from multiple providers by auto switching providers whenever required.

Let us look and understand the code.

We have to add two more libraries in our app > build.gradle file for using the location libraries. And sync the project with gradle clicking sync now.

// google location library implementation "com.google.android.gms:play-services-location:17.0.0" implementation "com.google.android.gms:play-services-maps:17.0.0"

First let us create class GoogleLocationHelper which will contain the logic for implementing the. Also don’t forget to implement the LocationHelperContract interface and override its methods as below.

Then within the constructor and implement the listeners LocationHelperContract and LocationListener. We have added @SuppressLint("MissingPermission") to remove the IDE’s warnings for permissions checking.

@SuppressLint("MissingPermission") class GoogleLocationHelper( private val application: Application, locationUpdateCallback: LocationUpdateCallback ) : LocationHelperContract { /** * Call this method to start location updates whenever required. */ override fun startGettingLocationUpdates() { } /** * Call this method when you need to stop getting request. */ override fun stopGettingLocationUpdates() { } }

Next we will want to update the class with a companion object with interval duration as below.

companion object { const val MIN_UPDATE_TIME: Long = 10 // in seconds }

And then after that we are going to implement the logic for location request which we will are required to create. But first let us prepare all the necessary variables.

// to prepare a different looper for location updates // basically run this on a different thread private val ht = HandlerThread("GoogleLocationHelper") private var isRunning = false // the client which will handle all the boilerplate and implementation details private val client = FusedLocationProviderClient(application) // the location request object which defines how we will be access the location private lateinit var mLocationRequest: LocationRequest

We are pretty much done except we need additional callback for getting the location updates in our activity class. This interface is defined here if you skipped the portion go back.

// the location callback method which will be updated once the new location is received private val locationCallback = object : LocationCallback() { override fun onLocationResult(p0: LocationResult?) { if (p0 == null) locationUpdateCallback.onNoLocation() else locationUpdateCallback.onLocationUpdate(p0.lastLocation) } override fun onLocationAvailability(p0: LocationAvailability?) { } }

Location request

We have to create a location request variable which defines our conditions for location request. The code below creates a LocationRequest with fastest interval, interval and priority set.

private fun prepareLocationRequest() { if (::mLocationRequest.isInitialized) return mLocationRequest = LocationRequest() .setInterval(TimeUnit.SECONDS.toMillis(MIN_UPDATE_TIME)) .setFastestInterval(TimeUnit.SECONDS.toMillis(MIN_UPDATE_TIME)) .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); }

What are those ?

fastest interval is the minimum time which the location updated should be requested. interval is the regular time when the location should be updated. priority is how the precisely the location should be determined.

Now that we have understood about location request let us move forward.

Getting location updates

Now we have come to the implementation part of the logic. Include the code below to request the location. The confusing part here is the ht.start(). ht is just a HandlerThread, to know about HandlerThread, read more.

We have to prepare the location request before getting location updates. Then we have requested the location updates.

override fun startGettingLocationUpdates() { if (isRunning) return isRunning = true ht.start() prepareLocationRequest() client.requestLocationUpdates(mLocationRequest, locationCallback, ht.looper) }

Stopping location updates

To stop our application from listening to new location updates. We have to implement the code below. The ht.quit() just quits the thread from which was started when requesting the updates.

override fun stopGettingLocationUpdates() { isRunning = false client.removeLocationUpdates(locationCallback) ht.quit() }

Now it’s time to look at the fully functional code.

@SuppressLint("MissingPermission") class GoogleLocationHelper( private val application: Application, locationUpdateCallback: LocationUpdateCallback ) : LocationHelperContract { // to prepare a different looper for location updates // basically run this on a different thread private val ht = HandlerThread("GoogleLocationHelper") private var isRunning = false // the client which will handle all the boilerplate and implementation details private val client = FusedLocationProviderClient(application) // the location request object which defines how we will be access the location private lateinit var mLocationRequest: LocationRequest // the location callback method which will be updated once the new location is received private val locationCallback = object : LocationCallback() { override fun onLocationResult(p0: LocationResult?) { if (p0 == null) locationUpdateCallback.onNoLocation() else locationUpdateCallback.onLocationUpdate(p0.lastLocation) } override fun onLocationAvailability(p0: LocationAvailability?) { } } private fun prepareLocationRequest() { if (::mLocationRequest.isInitialized) return mLocationRequest = LocationRequest() .setInterval(TimeUnit.SECONDS.toMillis(MIN_UPDATE_TIME)) .setFastestInterval(TimeUnit.SECONDS.toMillis(MIN_UPDATE_TIME)) .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } override fun startGettingLocationUpdates() { if (isRunning) return isRunning = true ht.start() prepareLocationRequest() client.requestLocationUpdates(mLocationRequest, locationCallback, ht.looper) } override fun stopGettingLocationUpdates() { isRunning = false client.removeLocationUpdates(locationCallback) ht.quit() } companion object { const val MIN_UPDATE_TIME: Long = 10 // in seconds } }

That concludes our logic for requesting location with the help of Google’s APIs. Which is only one of the way in getting GPS location in Android. Let us look at yet another way on how we can get location with the help of LocationManger.

The classic way

We will be using LocationManager to get the location.

In this path you will be directly managing the location settings with a low(ish) level API. You will be managing everything yourself, though there is not much to manage. We’ll see how this works later in the tutorial. This will work on all devices regardless of the applications installed in device.

PROS:
– Doesn’t depend on Google Location Libraries
– Much more freedom in implementation

CONS:
– Much more battery use ( if not taken care of properly )
– Have to write too much boilerplate code for it to work

Also Read: Bluetooth Bug: Leaves Multiple Vendors Vulnerable

TECHENUM

Let us look at the code now and understand how we can get location from GPS in android.

Let us continue with our class LocationHelper which we have setup here.

First declare variables within the constructor and implement the listeners LocationHelperContract and LocationListener. We have added @SuppressLint("MissingPermission") to remove the IDE’s warnings for permissions checking.

Oh and don’t forget to implement the methods from the interfaces as shown in the code.

@SuppressLint("MissingPermission") class LocationHelper( private val application: Application, private val provider: ProviderType, private val locationUpdateCallback: LocationUpdateCallback ) : LocationListener, LocationHelperContract { /** * Call this method to start location updates whenever required. */ override fun startGettingLocationUpdates() { } /** * Call this method when you need to stop getting request. */ override fun stopGettingLocationUpdates() { } override fun onLocationChanged(location: Location?) { } override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { } override fun onProviderEnabled(provider: String?) { } override fun onProviderDisabled(provider: String?) { } }

After that we want to setup few variables inside the same class as below.

// the location manager which has the main role here private val locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager // to explicitly find what kind of provider we are going to use private val mProviderType: String = if (provider == ProviderType.GPS) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER // for holding the location until next location is received private var mLastLocation: Location? = null private var isRunning = false

Once we have completed adding the above code we want to add the ProviderType enum class and other constant variables. The code below goes in companion object { } block.

/** * Just to identify the type of Provider */ enum class ProviderType { GPS, NETWORK } const val MIN_UPDATE_DISTANCE: Float = 20f // in meters val MIN_UPDATE_TIME: Long = TimeUnit.SECONDS.toMillis(10) // in milliseconds

Getting location updates

That is just the basic setup we have completed now the important part for the overriden block of code from LocationHelperContract. In the method startGettingLocationUpdates() we have the following code.

override fun startGettingLocationUpdates() { if (isRunning) return // do nothing because it is already running if (!EasyPermissions.hasPermissions( application, *PERMISSIONS ) ) return // we cannot continue because permissions has not been granted isRunning = true locationManager.requestLocationUpdates( mProviderType, MIN_UPDATE_TIME, MIN_UPDATE_DISTANCE, this ) }

The code above just does some basic checks and sets the flag and requests the location update with the defined time and interval.

Stopping location updates

Next the code to stop getting the location updates. And we’re almost done.

/** * Call this method when you need to stop getting request. */ override fun stopGettingLocationUpdates() { isRunning = false locationManager.removeUpdates(this) }

Our final code in the class looks like below, check yours for any errors or differences.

@SuppressLint("MissingPermission") class LocationHelper( private val application: Application, private val provider: ProviderType, private val locationUpdateCallback: LocationUpdateCallback ) : LocationListener, LocationHelperContract { // the location manager which has the main role here private val locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager // to explicitly find what kind of provider we are going to use private val mProviderType: String = if (provider == ProviderType.GPS) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER // for holding the location until next location is received private var mLastLocation: Location? = null private var isRunning = false private fun getLocation(): Location? { return mLastLocation } /** * Call this method to start location updates whenever required. */ override fun startGettingLocationUpdates() { if (isRunning) return // do nothing because it is already running if (!EasyPermissions.hasPermissions( application, *PERMISSIONS ) ) return // we cannot continue because permissions has not been granted isRunning = true locationManager.requestLocationUpdates( mProviderType, MIN_UPDATE_TIME, MIN_UPDATE_DISTANCE, this ) } /** * Call this method when you need to stop getting request. */ override fun stopGettingLocationUpdates() { isRunning = false locationManager.removeUpdates(this) } override fun onLocationChanged(location: Location?) { mLastLocation = location if (location == null) locationUpdateCallback.onNoLocation() else locationUpdateCallback.onLocationUpdate(location) } override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { } override fun onProviderEnabled(provider: String?) { } override fun onProviderDisabled(provider: String?) { } companion object { /** * Just to identify the type of Provider */ enum class ProviderType { GPS, NETWORK } const val MIN_UPDATE_DISTANCE: Float = 20f // in meters val MIN_UPDATE_TIME: Long = TimeUnit.SECONDS.toMillis(10) // in milliseconds const val REQUEST_CODE = 123 val PERMISSIONS = arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) } }

This is another way of getting GPS location in Android. But if you are not careful with this approach you will be draining a lot of battery. You have to highly optimize your code for this method. I suggest you to stick the recommended way of getting GPS location in Android i.e. through FusedLocationProviderClient.

Connecting the pieces, the working code

Now that we have written our logic for handling locations in multiple ways. Let us look at how we can use them in our activity. Here my activity is MainActivity and the code lies below.

We have already discussed the parts of MainActivity in this section here. SO, let us quickly look at how to start getting location.

Now let us declare another variable called locationUpdateCallback, remember from here ? This is our implementation for receiving the location updates.

You might want to change the code in onLocationUpdate() to your own implementations. Here, I have just printed out the new location.

class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { private val locationUpdateCallback = object : LocationUpdateCallback { override fun onLocationUpdate(location: Location) { // do something with the location Log.d( "MainActivity", "latitude: ${location.latitude}, longitude: ${location.longitude}" ) } override fun onNoLocation() { // there was no location updates } } }

Also lets add some more variable and methods. In the code below we have created a variable locationHelper which will hold the type of location request, thanks to polymorphism. You learn more about polymorphism I suggest – Interface in OOP: Guide to Polymorphism and Callbacks.

class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { private lateinit var locationHelper: LocationHelperContract private fun getGoogle() { locationHelper = GoogleLocationHelper(application, locationUpdateCallback) locationHelper.startGettingLocationUpdates() } private fun getClassic() { locationHelper = LocationHelper( application, LocationHelper.Companion.ProviderType.GPS, locationUpdateCallback ) locationHelper.startGettingLocationUpdates() } }

Now all that there is left to do invoke the required method in initSequence() and you’re done. We have successfully acquired the GPS location in Android. Let us look at the complete code below.

class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { private lateinit var locationHelper: LocationHelperContract private val locationUpdateCallback = object : LocationUpdateCallback { override fun onLocationUpdate(location: Location) { // do something with the location Log.d( "MainActivity", "latitude: ${location.latitude}, longitude: ${location.longitude}" ) } override fun onNoLocation() { // there was no location updates } } private fun initSequence() { // getClassic() getGoogle() } private fun getGoogle() { locationHelper = GoogleLocationHelper(application, locationUpdateCallback) locationHelper.startGettingLocationUpdates() } private fun getClassic() { locationHelper = LocationHelper( application, LocationHelper.Companion.ProviderType.GPS, locationUpdateCallback ) locationHelper.startGettingLocationUpdates() } private fun hasRequiredPermissions(): Boolean { return EasyPermissions.hasPermissions(this, *LocationHelper.PERMISSIONS) } private fun requestRequiredPermissions() { EasyPermissions.requestPermissions( this, getString(R.string.permission_rationale), LocationHelper.REQUEST_CODE, *LocationHelper.PERMISSIONS ) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) LocationUtil.turnOnGpsIfNot(this) requestPermissionIfNotAndContinue() } @AfterPermissionGranted(LocationHelper.REQUEST_CODE) fun requestPermissionIfNotAndContinue() { if (hasRequiredPermissions()) { // we have permission do things initSequence() } else { // request permission requestRequiredPermissions() } } override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) { // continually ask for permission until it has been granted requestPermissionIfNotAndContinue() } override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) { // permission has been granted invoke the method again requestPermissionIfNotAndContinue() } }

Known issue

Earlier I had mentioned some issue with FusedLocationProviderClient and the issue is that if you set the

.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)

to the code below

.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)

For some weird reasons it will not work. Do you know what’s causing this ? And how might we solve this ? Feel free to comment below.

COMING SOON: GitHub repository for this article about getting GPS location in Android.

Keep learning Keep coding.

Naveen Niraula

I am an Android Developer. I really love to write tutorials and reviews about latest technologies.

Leave a Reply