To request the last known location of the user's device, we can use the fused location provider to retrieve the device's last known location using getLastLocation(), but using getCurrentLocation() gets a refresher, and more accurate location.
so, how to use the fusedLocationClient.getCurrentLocation() in Kotlin as there is no example illustrated in the documentation?
According to the documentation, the getCurrentLocation() takes two parameters.
The 1st parameter it takes is the priority (e.g. PRIORITY_HIGH_ACCURACY) to request the most accurate locations available, or any other priority that can be found here.
The 2nd parameter it takes is a cancellation token that can be used to cancel the current location request.
From the Google play services reference, a CancellationToken can only be created by creating a new instance of CancellationTokenSource.
so here is the code you need to use when using getCurrentLocation()
class YourActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.your_layout)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = location.latitude
val lon = location.longitude
}
}
}
}
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(listener: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener {
if (it == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = it.latitude
val lon = it.longitude
}
}
I know the questio is about Kotlin but I want to add for those searching for Java :
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, cancellationTokenSource.getToken())
.addOnSuccessListener(MyActivity.this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
//do your thing
}
Log.w(TAG, "No current location could be found");
}
});
Related
After 2 days of struggle, I have been forced to ask this question. I have been searching StackOverflow but no solution seems to work for me. I don't know why.
I am trying to get the current device location once:
(a) the permissions have been granted
(b) user has enabled location service by tapping 'Okay' in the dialogue box that appears in the fragment (SelectLocationFragment.kt)
The issue: Once the permissions are granted, location is enabled by pressing 'okay' in the dialogue - If I use hard coded values like here:
//currentLatLng = LatLng(51.000, -0.0886)
instead of :
currentLatLng = LatLng(mCurrentLocation.latitude,mCurrentLocation.longitude)
It works fine. But this is not ideal of course. I'd like real time device location using onLocationCallBack's onLocationResult using:
fusedLocationClient.requestLocationUpdates(locationRequest,locationCallback, Looper.getMainLooper())
The problem is when I try to use:
currentLatLng = LatLng(mCurrentLocation.latitude,mCurrentLocation.longitude)
a) onLocationCallBack does not seem to be called. And therefore no call to onLocationResult happens, and therefore, mCurrentLocation remains uninitialised. And hence I get the error :
kotlin.UninitializedPropertyAccessException: lateinit property mCurrentLocation has not been initialized
This my onMapReady()
#SuppressLint("MissingPermission")
override fun onMapReady(gMap: GoogleMap) {
isPoiSelected = false
map = gMap
**checkPermissionsAndDeviceLocationSettings()**
setMapStyle(map)
setPoiClick(map)
setMapLongClick(map)
}
This is checkPermissionAndDeviceLocationSettings():
#SuppressLint("MissingPermission")
private fun checkPermissionsAndDeviceLocationSettings() {
if (isPermissionGranted()) {
checkDeviceLocationSettings()
} else {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_PERMISSION_LOCATION
)
}
}
This is checkDeviceLocationSettings:
private fun checkDeviceLocationSettings(resolve: Boolean = true) {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 10000L
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val settingsClient = LocationServices.getSettingsClient(requireActivity())
val locationSettingsResponseTask =
settingsClient.checkLocationSettings(builder.build())
locationSettingsResponseTask.addOnFailureListener { exception ->
if (exception is ResolvableApiException && resolve) {
try {
startIntentSenderForResult(
exception.resolution.intentSender,
REQUEST_TURN_DEVICE_LOCATION_ON, null, 0, 0, 0, null
)
} catch (sendEx: IntentSender.SendIntentException) {
Log.d(
SaveReminderFragment.TAG,
"Error getting location settings resolution: " + sendEx.message
)
}
} else {
Snackbar.make(
activity!!.findViewById<CoordinatorLayout>(R.id.myCoordinatorLayout),
R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) {
checkDeviceLocationSettings()
}.show()
}
}
locationSettingsResponseTask.addOnSuccessListener {
Log.e(TAG, "SUCCESSFUL!")
Toast.makeText(requireContext(), "TOAST", Toast.LENGTH_SHORT).show()
enableLocation(locationRequest)
showSnackBar(getString(R.string.select_location))
}
}
This is enableLocation:
#SuppressLint("MissingPermission")
private fun enableLocation(locationRequest: LocationRequest) {
Log.e(TAG, "Inside Enable Location Start")
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
Log.e(TAG, "Inside on Location Result")
val locationList = locationResult.locations
Log.e(TAG, "${locationResult.locations}")
if(locationList.isNotEmpty()){
val location = locationList.last()
Log.i("MapsActivity", "Location: " + location.latitude + " " + location.longitude)
mCurrentLocation = location
}
fusedLocationClient.removeLocationUpdates(locationCallback)
}
}
map.isMyLocationEnabled = true
val locationResult: Task<Location> = fusedLocationClient.lastLocation
locationResult.addOnCompleteListener(OnCompleteListener<Location?> { task ->
if (task.isSuccessful) {
Log.e(TAG, locationResult.result?.latitude.toString())
// Set the map's camera position to the current location of the device.
if (task.result != null) {
mCurrentLocation = task.result!!
val latLng = LatLng(
mCurrentLocation.latitude,
mCurrentLocation.longitude
)
val update = CameraUpdateFactory.newLatLngZoom(
latLng,
18f
)
Toast.makeText(requireContext(), "CAMERA MOVING", Toast.LENGTH_SHORT).show()
map.animateCamera(update)
} else {
Log.e(TAG, " Task result is null")
//Need to do something here to get the real time location
fusedLocationClient.requestLocationUpdates(locationRequest,locationCallback, Looper.getMainLooper())
currentLatLng = LatLng(mCurrentLocation.latitude,mCurrentLocation.longitude)
//currentLatLng = LatLng(51.000, -0.0886)
val update = CameraUpdateFactory.newLatLngZoom(currentLatLng, 18f)
map.animateCamera(update)
}
} else {
Log.e(TAG, "Unsuccessful Task result")
Toast.makeText(requireContext(), "ENABLE LOCATION ELSE", Toast.LENGTH_LONG).show()
}
})
}
Is there any God sent man out there to help me resolve this issue. I am beginning to fade away.
If you guys need to see the entire file:
Thank you in advance.
FusedLocationProvider can return a null Location, especially the first time it fetches a Location. So, as you have found, simply returning when the result is null will force requestLocationUpdates() to fire again and actually fetch a valid Location the second time.
I think you can also force FusedLocationProvider to have a Location the first time if you just open the Google Maps app and wait for it to obtain a GPS lock. Of course, you wouldn't want your users to have to perform this process in order for your app to work, but it's still good to know for debugging.
The cause of this seems to be that FusedLocationProvider tries to return the most recently fetched Location for the device. If this Location is too old, has never been fetched, or the user toggled on/off the Location option on their phone, it just returns null.
I'm learning Kotlin, and I'm trying to make a simple app that displays the users long/lat. Everything seems like it should be working, but the location data keeps throwing the 'null else{}' case. Below is what my code looks like.
class MainActivity : AppCompatActivity() {
val RequestPermissionCode = 1
var mLocation: Location? = null
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
getLastLocation()
}
fun getLastLocation(){
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermission()
}
else{
fusedLocationClient.lastLocation
.addOnSuccessListener {location: Location? ->
mLocation = location
if(location != null){
latitude.text = location.latitude.toString()
longitude.text = location.longitude.toString()
}
else{
latitude.text = "LOCATION_DENIED"
longitude.text = "LOCATION_DENIED"
}
}
}
}
private fun requestPermission(){
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), RequestPermissionCode)
this.recreate()
}
}
Any help would be greatly appreciated! I'm about to start pulling my hair out, and can't find the answers on Google ):
Emulators I have noticed have issues with Location data, plus lastLocation is not making a location request, so there is good chance there is no location data. What you need is requestLocationUpdate method, so that it can retrieve active location of the user. Also you will have better luck on a physical device for last Location.
My app works fine if using foreground service to get user location, however in background I just need location for each 15 minutes, also Google requires new Policy for getting background location so, foreground service is over my expectation.
I am trying to get location from background using WorkManager, it can run normally every (around) 15 minutes. My location is requested, however it always returns the previous address, even 1, 2... hours are passed.
Here is my code:
class LocationWorker(private val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private var fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
override suspend fun doWork() = withContext(Dispatchers.IO) {
val location = getLocation()
if (location == null) {
if (runAttemptCount < MAX_ATTEMPT) { // max_attempt = 3
Result.retry()
} else {
Result.failure()
}
} else {
Log.d(TAG, "doWork success $location")
Result.success()
}
}
private suspend fun getLocation(): Location? = withTimeoutOrNull(TIMEOUT) {
suspendCancellableCoroutine<Location?> { continuation ->
val intent = PendingIntent.getBroadcast(context, REQUEST_CODE, Intent(ACTION), 0)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, data: Intent?) {
if (data?.action != ACTION) return
val lastLocation = LocationResult.extractResult(data)?.lastLocation
Log.e(TAG, "Get lastLocation success $lastLocation")
fusedLocationClient.removeLocationUpdates(intent)
context?.unregisterReceiver(this)
continuation.resume(lastLocation)
}
}
context.registerReceiver(receiver, IntentFilter(ACTION))
val request = LocationRequest().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY }
fusedLocationClient.requestLocationUpdates(request, intent)
continuation.invokeOnCancellation {
fusedLocationClient.removeLocationUpdates(intent)
context.unregisterReceiver(receiver)
}
}
companion object {
val TAG = LocationWorker::class.java.simpleName
const val LOCATION_WORKER_TAG = "LOCATION_WORKER_TAG"
const val MAX_ATTEMPT = 3
private const val ACTION = "my.background.location"
private const val TIMEOUT = 60_000L
private const val REQUEST_CODE = 888
}
}
Pre-condition:
Tested device: emulator android 27 (O_MR1)
Route Play normally
GPS is enabled
Allow location permission (allow all time)
Why the lastknown location not updated ?
I also tried this demo https://github.com/pratikbutani/LocationTracker-WorkManager/ . However, the problem is same, lastknown location is not updated.
The previous location gets cached.
Clear the cache before you store the new location in lastknown location
I am trying to create a method that will get the last known location and assign that value to a global variable that I will use later. I am following this training.
In the end, this is the code that I've wrote:
public void getCurrentLocation() {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
fusedLocationClient.getLastLocation().addOnSuccessListener(this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
// Logic to handle location object
mCurrentLocation = new Location(location);
System.out.println("In method getCurrentLocation: Latitude = " + location.getLatitude() + " Longitude = " + location.getLongitude());
System.out.println("In method getCurrentLocation for mCurrentLocation: Latitude = " + mCurrentLocation.getLatitude() + " Longitude = " + mCurrentLocation.getLongitude());
} else {
Toast.makeText(MainActivity.this, "Can't get the current location", Toast.LENGTH_SHORT).show();
}
}
});
}
The getCurrentLocation() method I will call in the onCreate() method of MainActivity and I can see the prints from the method so my variable got a location, but if I try to use the mCurrentLocation in some other context (for example I want to go to the last known location using this variable) the variable will be null. I am not sure that I need new Location(location), I added it because I thought that my variable will point to a null place in the memory after we exit the method, but it looks that it is not the case (I get the same outcome).
According to official documentation, Last Known Location could be Null in case of:
Location is turned off in the device settings. As it clears the
cache.
The device never recorded its location. (New device)
Google Play services on the device has restarted.
In this case, you should requestLocationUpdates and receive the new location on the LocationCallback.
By the following steps your last known Location never null.
Pre-requisite:
EasyPermission library
Step 1:
In manifest file add this permission
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Step 2:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Create location callback when it's ready.
createLocationCallback()
//createing location request, how mant request would be requested.
createLocationRequest()
//Build check request location setting request
buildLocationSettingsRequest()
//FusedLocationApiClient which includes location
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
//Location setting client
mSettingsClient = LocationServices.getSettingsClient(this)
//Check if you have ACCESS_FINE_LOCATION permission
if (!EasyPermissions.hasPermissions(
this#MainActivity,
Manifest.permission.ACCESS_FINE_LOCATION)) {
requestPermissionsRequired()
}
else{
//If you have the permission we should check location is opened or not
checkLocationIsTurnedOn()
}
}
Step 3:
Create required functions to be called in onCreate()
private fun requestPermissionsRequired() {
EasyPermissions.requestPermissions(
this,
getString(R.string.location_is_required_msg),
LOCATION_REQUEST,
Manifest.permission.ACCESS_FINE_LOCATION
)
}
private fun createLocationCallback() {
//Here the location will be updated, when we could access the location we got result on this callback.
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
mCurrentLocation = locationResult.lastLocation
}
}
}
private fun buildLocationSettingsRequest() {
val builder = LocationSettingsRequest.Builder()
builder.addLocationRequest(mLocationRequest!!)
mLocationSettingsRequest = builder.build()
builder.setAlwaysShow(true)
}
private fun createLocationRequest() {
mLocationRequest = LocationRequest.create()
mLocationRequest!!.interval = 0
mLocationRequest!!.fastestInterval = 0
mLocationRequest!!.numUpdates = 1
mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
public fun checkLocationIsTurnedOn() { // Begin by checking if the device has the necessary location settings.
mSettingsClient!!.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(this) {
Log.i(TAG, "All location settings are satisfied.")
startLocationUpdates()
}
.addOnFailureListener(this) { e ->
val statusCode = (e as ApiException).statusCode
when (statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
try {
val rae = e as ResolvableApiException
rae.startResolutionForResult(this#MainActivity, LOCATION_IS_OPENED_CODE)
} catch (sie: IntentSender.SendIntentException) {
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
mRequestingLocationUpdates = false
}
}
}
}
private fun startLocationUpdates() {
mFusedLocationClient!!.requestLocationUpdates(
mLocationRequest,
mLocationCallback, null
)
}
Step 4:
Handle callbacks in onActivityResult() after ensuring the location is opened or the user accepts to open it in.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
LOCATION_IS_OPENED_CODE -> {
if (resultCode == AppCompatActivity.RESULT_OK) {
Log.d(TAG, "Location result is OK")
} else {
activity?.finish()
}
}
}
Step 5:
Get last known location from FusedClientApi
override fun onMapReady(map: GoogleMap) {
mMap = map
mFusedLocationClient.lastLocation.addOnSuccessListener {
if(it!=null){
locateUserInMap(it)
}
}
}
private fun locateUserInMap(location: Location) {
showLocationSafetyInformation()
if(mMap!=null){
val currentLocation = LatLng(location.latitude,location.longitude )
addMarker(currentLocation)
}
}
private fun addMarker(currentLocation: LatLng) {
val cameraUpdate = CameraUpdateFactory.newLatLng(currentLocation)
mMap?.clear()
mMap?.addMarker(
MarkerOptions().position(currentLocation)
.title("Current Location")
)
mMap?.moveCamera(cameraUpdate)
mMap?.animateCamera(cameraUpdate)
mMap?.setMinZoomPreference(14.0f);
}
I hope this would help.
Happy Coding 🤓
A few weeks later I came up with some sort of a explanation to what is happening and how I managed to get the result that I want. I only had to do the first 3 steps that #MustafaKhaled wrote in his answer, this will make the mFusedLocationClient global variable to have its getLastLocation() method called (I think this happens because I request location updates, see #MustafaKhaled post).
Now, what we do with the last location? The response is nothing, because we do not handle what will happen if we successfully retrieve the location.
That being said, we will need to add what will happen onSuccess() with the location. In my case, I had to retain the value of the mCurrentLocation so I still need the method from the initial post to be implemented:
public void AddFusedLocationClientOnSuccessListener() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
mFusedLocationClient.getLastLocation()
.addOnSuccessListener(this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
// Logic to handle location object
mCurrentLocation = location;
}
}
});
}
Here I will be able to do some other things with the location if needed (for example if I want the application to animate to the current location), but later, if I when I use buttons or other graphical elements, I will be able to use the mCurrentLocation variable.
Now, I will be able to call the mCurrentLocation directly in the onCreate()? The answer is NO, because we will have to wait for mFusedLocationClient to successfully get the last known location, otherwise themCurrentLocation will be null, that is the reason in the initial post it did not work, we were not calling the getLastLocation() method until we will get success.
I hope this answer will be helpful for other peoples that encounter this problem and they don't fully understand what happens in the onCreate().
I must have to take latitude and longitude of the user when user first time open an application.
So far, I have done as below :
Necessary Variables :
//Location Utils below :
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var lastLocation: Location
private lateinit var currentLatLng: LatLng
On Button Click : Checking the permission for the location, if has permission calling method named onLocationGranted() else asking for the location permission.
if (EasyPermissions.hasPermissions(
mContext,
FilePickerConst.PERMISSIONS_FINE_LOCATION
)
) {
onLocationGranted()
} else {
// Ask for one permission
EasyPermissions.requestPermissions(
this,
getString(R.string.permission_location),
Constant.MultiMediaRequestCode.LOCATION_PERMISSION,
FilePickerConst.PERMISSIONS_FINE_LOCATION
)
}
Below is the method onLocationGranted() : Here, Initializing LocationManager, Checking that GPS is on or not. If GPS on or enabled, taking location which is done in method named : getAndSaveCurrentLocation(). If, GPS is off or not enabled calling the method named buildAlertMessageNoGps()
val manager = mContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
//If GPS is not Enabled..
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
buildAlertMessageNoGps()
} else {
getAndSaveCurrentLocation()
}
Method getAndSaveCurrentLocation() is as below : In which I am taking location using fusedapi.
private fun getAndSaveCurrentLocation() {
try {
fusedLocationClient =
LocationServices.getFusedLocationProviderClient(mContext as AppBaseActivity)
fusedLocationClient.lastLocation.addOnSuccessListener(mContext as AppBaseActivity) { location ->
// Got last known location. In some rare situations this can be null.
if (location != null) {
lastLocation = location
currentLatLng = LatLng(location.latitude, location.longitude)
if (currentLatLng != null &&
currentLatLng.latitude != null &&
currentLatLng.longitude != null
) {
sharedPreferenceManager?.setStringData(
Constant.PrefKey.userLatitude,
"" + currentLatLng.latitude
)
sharedPreferenceManager?.setStringData(
Constant.PrefKey.userLongitude,
"" + currentLatLng.longitude
)
}
}
(mContext as AppBaseActivity).supportFragmentManager.popBackStack()
(activity as AppBaseActivity).addFragmentToRoot(
DashboardFragmentNew.newInstance(),
false
)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Method buildAlertMessageNoGps() is as below : If GPS is off then, I am calling this method.
private fun buildAlertMessageNoGps() {
val builder = AlertDialog.Builder(mContext)
builder.setMessage("Your GPS seems to be disabled, do you want to enable it?")
.setCancelable(false)
.setPositiveButton("Yes", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, id: Int) {
startActivity(Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
})
.setNegativeButton("No", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, id: Int) {
dialog.cancel()
}
})
val alert = builder.create()
alert.show()
}
Now, the above method opens the settings to turn on GPS.
My question is : Suppose user turns on GPS, and coming back to screen, How can I get location then ?
Or If user not turning on GPS there, in that case How can I take location?
Thanks.
Suppose user turns on GPS, and coming back to screen, How can I get location then ?
You will get a call back to onActivityReenter similar to onActivityResult. So Override that method and call getAndSaveCurrentLocation() there.