Im trying to getLastLocation(), but sometimes it is null. When that happens, I go to google maps just for a second, and return to my app and in 99% that will do. There is also one app that just returns city that you're in and it works even if my app can't getLastLocation(). I've noticed that when I use that other app, or google maps, or weather app, for a short time location icon will appear in status bar, but when I use my app that icon never appears, so I'm guessing that may be the problem?
What I need to do to assure that I get my location to be != null?
One more thing, sometimes I get my location (lat and long), but reverse geocoding goes to catch because List is empty? How to make sure it always is not empty?
The code that I use is just a copy/past from android developers.
If you are using Android Emulator it is expected that the location doesn't get updated unless you open the Maps App.
To ensure you get non-null location you need to request for location updates
You can do something like this
#SuppressLint("MissingPermission")
private fun getLastKnownLocation() {
// get last known location from fusedLocationProviderClient returned as a task
fusedLocationProviderClient.lastLocation
.addOnSuccessListener { lastLoc ->
if (lastLoc != null) {
// initialize lastKnownLocation from fusedLocationProviderClient
lastKnownLocation = lastLoc
} else {
// prompt user to turn on location
showLocationSettingDialog()
// when user turns on location trigger updates to get a location
fusedLocationProviderClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper()
)
}
// in case of error Toast the error in a short Toast message
}
.addOnFailureListener {
Toast.makeText(requireActivity(), "${it.message}", Toast.LENGTH_SHORT).show()
}
}
This is just a stub, you will need to handle permissions, create FusedLocationProviderClient, LocationRequest and LocationCallbackObject.
You may also need to prompt the user to Turn on Location Settings.
Please show us your GeoCoding code to elaborate further.
Related
i implemented the method used in Lib android this, see:
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
if(location != null) {
localidade = location;
Toast.makeText(getApplicationContext(), "Local correto", Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Localidade vazia", Toast.LENGTH_LONG).show();
}
});
However, within OnSuccess the result is ALWAYS null, I am testing on my mobile (9.0 android) with LOCAL(gps) mode on and still returns null, my manisfest has the necessary permissions: <uses-permission android: name = "android.permission .ACCESS_FINE_LOCATION "/>
In my view nothing is wrong, so why ALWAYS null?
Last known location is registered known location by the user. If you read on the location page it states that last known location can be null in certain situations; in those scenarios it is advised you request location updates. Following is taken from the page:
The location object can be nullin the following situations:
Location is disabled in the device settings. The result may nulleven
be that the last location was retrieved earlier, because disabling the
location also clears the cache. The device never recorded its own
location, which happens when the device is new or when it has been
restored to factory settings. The Google Play Services platform on the
device has been restarted and no active Fused Location Provider API
clients have requested location after services have been restarted. To
avoid this situation, create a new customer and request location
updates.
I have read the Android Docs, FusedLocationProvider vs LocationManager; perused the dizzying array of questions and answers around this topic here in stackoverflow; and developed many tests with poor results so far. Why is this so darned confusing and hard to grasp?
I have an app that needs to get a hi-res Location object (lat/long/alt/accuracy/etc) when the user performs an action in the app; let's say they press a button. What is the best way to do this?
I have used the fusedLocationProviderClient.getLastLocation().addOnSuccessListener() and get wildly mixed results.
I have used locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER). If I start the GPS Status app on my Galaxy S9 then this produces quite wonderful results. But if that is not running, then the results are worthless.
What am I missing here? Everyone likes to point to this Doc site or that Example site that is mostly worthless and doesn't really answer this specific question. I have wasted hours pouring over those sites that simply don't answer this question. Please, just sum up the general algorithm that should be used here and the calls to make. That is all I need.
I want to be able to walk around in my yard (10 meters here and there) and press the button and have the app show the lat/long/accuracy/altitude/distance-from-last-location and have it be correct every time within a certain level of accuracy. What do I have to do? I need hi-res accuracy, but the ability to notify the user of accuracy less than say 100ft, and still obtain the best accuracy possible even if it has an error of 400ft.
You are missing how GPS receivers work.
When there is no app using precise location, all smartphones turn off the GPS receiver to conserve battery power.
Even if you selected location services to be on (in settings), you will notice in the notification bar the icon for GPS use is only present when an app is active, like Google Maps or GPS test app.
Once the receiver is turned on (because some app needs it), it takes some time before a "fix" - accurate location measurement is available.
How long it will take to get a fix depends on several things, including environmental conditions, your phone type, time and distance since last accurate fix, etc.
It may take anywhere from several seconds to sever minutes.
So, what you should do, is subscribe to location as soon as your app is opened, and request to receive it as frequently as possible.
Then, enable the button only once you have good accuracy, and when the button is pressed, show the latest result.
You should probably also display some spinner or message to the user while waiting for accurate fix so the user knows your app is not stuck.
Edit: by "subscribe" I mean register the necessary callback so your app will receive the location from the system when it is ready.
How to do this, depends on which API you choose.
There is no error in the google docs.
If you choose to use fused location, you will need to do the following:
Create a location request object and set priority to PRIORITY_HIGH_ACCURACY, also setInterval and setFastestInterval to 1000 (1 second) to get the best accuracy.
Get a FusedLocationProviderClient object from LocationServices
Use the client to register a callback to your app
There are code examples here:
https://developer.android.com/training/location/request-updates
In the callback function in your app you can check the accuracy, and if it is good enough for you enable the button and save the location so you can display it to the user when they click the button.
Ok - this seems to work. This general flow seems to be the answer.
Assumptions: you are requesting android.permission.ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION in manifest.
Just sample code in the onCreate() function of MainActivity for testing purposes.
check to see if we have ACCESS_FINE_LOCATION permission; if not, request.
get FusedLocationProviderClient
get a start location from getLastLocation(); for purposes of comparison and start of track
define locationCallback() to be called by fusedLocationProvider; all we are interested in is getting the last one in the stack and save to class Field.
define LocationRequest with interval of 5 secs and PRIORITY_HIGH_ACCURACY
check to see if the user device is allowing this; not sure what to do with this other than to notify user if not allowed.
now requestLocationUpdates using the LocationCallback defined above.
when user performs action needing current lat/long (e.g. press button), retrieve class field populated with Location object on last LocationCallback().
I am very open to feedback on this pattern. Hope it helps others (as there is a plethora of questions about this). And would love to hear about any problems with this design or issues that I may encounter.
if (Build.VERSION.SDK_INT >= 23) {
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE_ASK_PERMISSIONS);
} else {
// getFusedLocationProviderClient
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
// getStartLocation
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
if (location != null) {
StartLocation.set(location);
}
}
}).addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
});
// Define LocationCallback
locationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
LastLocation = locationResult.getLastLocation();
}
}
};
// Now lets request location updates - that is how this must happen
// https://developer.android.com/training/location/change-location-settings
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(1000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Attempt to see if requested settings are compatible with user device.
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locationRequest);
// Check to see if location settings are satisfied by user's device settings?
SettingsClient client = LocationServices.getSettingsClient(this);
Task<LocationSettingsResponse> locationTask = client.checkLocationSettings(builder.build())
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
#Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
}
}).addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
if (e instanceof ResolvableApiException) {
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
}
Toast.makeText(MainActivity.this, "Location Settings Are Not " +
"Correct On This Device", Toast.LENGTH_LONG).show();
}
});
// Request location updates
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
}
}
I am developing a simple app using Flutter's location plugin, with some code based on their sample code:
var location = new Location();
try {
_currentLocation = await location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
_locationMsg = 'Permission denied';
}
_currentLocation = null;
}
As indicated in the plugin page, I added ACCESS_FINE_LOCATION to the Android manifest.
The problem is, when I test the app on a phone (with Android 9, in case it's relevant), even though location is enabled and I have a GPS signal, executing the above code results in the following prompt:
The prompt reads: "For a better experience, turn on device location, which uses Google's location service.", with two buttons: "NO THANKS" and "OK".
This is horribly user-unfriendly: location is already coming from the GPS, there is no need to further bother the user.
Where is the problem coming from, and how can I avoid that prompt? I prefer reporting "unknown location" than having the prompt displayed.
Edit: Note that the prompt is not related to notifying the user that the location will be used, but it is a Google privacy-invading feature that, when you click OK, enables Google Location Accuracy, as described below (hidden deep in a Settings menu):
The above image reads: "Improve Location Accuracy", with a toggle button. Google Location Accuracy: Google’s location service improves location accuracy by using Wi‑Fi and mobile networks to help estimate your location. Anonymous location data will be sent to Google when your device is on.
Clicking on the first prompt enables this, which the user then has to manually disable if they don't want to send their location data to Google. Disabling it and trying to get the location again results in the same prompt, so it is definitely not related to warning the user about the usage of location data. Also, if Google Location Accuracy is enabled before using the app, the prompt never appears in the first place, which is probably why most developers never notice it.
I know it is possible to get location data without enabling Google Location Accuracy, since most apps do it. But I don't know where the prompt comes from: is it Flutter's location plugin? The fact that I am using an Android 9 SDK? Or the sample code?
It seems the issue is coming from the location plugin. I tried replacing it with geolocator, and modifying the caller code, and this time no such prompt appears.
I tried lowering the accuracy before asking for location, but the location plugin still displays the prompt. There must be some underlying code which is hardwired to request Google Location Accuracy in all cases.
If only Google would provide a way to permanently disable the prompt with a systematic
"no" (this has been an issue for several Android releases), I might have given them the benefit of doubt.
I was having this same issue today but solved it by locating the offending expression and added an if statement to check whether the app's location permission was granted.
Here is my code in Kotlin and the offending expression is under the note in all caps:
private fun checkLocationPermission() : Boolean = (ContextCompat.checkSelfPermission(
requireContext(), ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
private fun startLocationRequests() {
val locationRequest = LocationRequest.create()?.apply {
interval = 10000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest!!)
val client: SettingsClient = LocationServices.getSettingsClient(requireActivity())
val task: Task<LocationSettingsResponse> = client.checkLocationSettings(builder.build())
task.addOnSuccessListener { locationSettingsResponse ->
// All location settings are satisfied. The client can initialize
// location requests here.
// ...
if (checkLocationPermission()) {
locationPermissionGranted = true
fusedLocationProviderClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper())
}
}
task.addOnFailureListener { exception ->
if (exception is ResolvableApiException){
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
// IF STATEMENT THAT PREVENTS THE DIALOG FROM PROMPTING.
if (checkLocationPermission()) {
exception.startResolutionForResult(
requireActivity(),
REQUEST_CHECK_SETTINGS
)
}
} catch (sendEx: IntentSender.SendIntentException) {
// Ignore the error.
}
}
Timber.i("Location Listener failed")
}
}
Now my solution may not exactly apply to your problem but I think it should be enough to learn from to solve your problem. However, you may need to redo your code when it comes to requesting a user's location, though.
Also, I'm not so sure about using Flutter's plugin for location but in your case and others, I would recommend following the developer documentation when it comes to requesting a user's location. Perhaps it's applicable to Flutter too:
https://developer.android.com/training/location/change-location-settings
Google's FusedLocationProviderApi for Android was recently deprecated within the past few months, with FusedLocationProviderClient being its successor so I recently updated the location APIs used in my client's app to use the new ones.
Every time onLocationAvailability is fired in LocationCallback I notify the user when locationAvailability.isLocationAvailable() returns false, but it appears that this condition occurs more often than I expected on some devices. I run these location updates inside a foreground Service and it is crucial that these location updates remain consistent. Is there a way to determine the cause of this failure so
We don't indicate any false positives to the end-user
We can try to fix the issue or at least report to the end-user what they should do?
It appears to me that either the deprecated APIs provide more insight into these issues since it was used in conjunction with GoogleApiClient or perhaps I'm missing some smaller details.
I went through the same issue. And after three days of trying things out, I got to work on it out.
I also had to collect a location in a foreground state like you, and if the foreground service was destroyed, I had to unregister.
The first mistake I made was not to guarantee that removeLocationUpdates would be run on the same thread as the requestLocationUpdates. Actually, it doesn't have to be the same thread, but after a requestLocationUpdates, you must call removeLocationUpdates to make the next requestLocationUpdates valid. To ensure this, it is much easier to work on the same thread.
For example:
private fun FusedLocationProviderClient.requestLocation(
request: LocationRequest
): Single<LocationResult> {
return Single.create<LocationResult> { emitter ->
requestLocationUpdates(request, object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
removeLocationUpdates(object : LocationCallback() {})
.addOnCompleteListener {
if (emitter.isDisposed) {
info("onLocationResult called after disposing.")
return#addOnCompleteListener
}
if (result != null && result.locations.isNotEmpty()) {
onSuccess(result)
} else {
onError(RuntimeException("Invalid location result"))
}
}
}
private fun onError(error: Exception) {
if (!emitter.isDisposed) {
emitter.onError(error)
}
}
private fun onSuccess(item: LocationResult) {
if (!emitter.isDisposed) {
emitter.onSuccess(item)
}
}
}, Looper.getMainLooper())
}
}
As the code suggests, I have attracted Single's emitter to the addOnCompleteListener in removeLocationUpdates to ensure the call of removeLocationUpdates behind the requestLocationUpdates. Without RxJava, of course, it would be easier to implement.
The second mistake I made was the wrong interval setting in LocationRequest. According to the doc:
This method sets the rate in milliseconds at which your app prefers to receive location updates. Note that the location updates may be somewhat faster or slower than this rate to optimize for battery usage, or there may be no updates at all (if the device has no connectivity, for example).
The explanation is unkind but ultimately, if you call requestLocationUpdates once, you must have a Location update event triggered by interval before the next requestLocationUpdates. Finding this bug was the hardest.
The third mistake I made was to set the wrong priority in LocationRequest. In API 10 and below, it was not PRIORITY_BALANCED_POWER_ACCURACY but it was resolved by using PRIORITY_HIGH_ACCURACY. In this case, I have only tested on the emulator, so the actual device may have different results. I guess PRIORITY_BALANCED_POWER_ACCURACY doesn't seem to work properly because the emulator doesn't provide Bluetooth hardware.
So my LocationRequest looks like:
LocationRequest.apply {
priority = PRIORITY_HIGH_ACCURACY
interval = 10000L
}
I hope the three mistakes and solutions I made is helpful to you!
As official documentation says:
onLocationAvailability Called when there is a change in the
availability of location data. When isLocationAvailable() returns
false you can assume that location will not be returned in
onLocationResult(LocationResult) until something changes in the
device's settings or environment. Even when isLocationAvailable()
returns true the onLocationResult(LocationResult) may not always be
called regularly, however the device location is known and both the
most recently delivered location and getLastLocation(GoogleApiClient)
will be reasonably up to date given the hints specified by the active
LocationRequests.
So this method does not provide information about reason.
We don't indicate any false positives to the end-user
Currently I just ignore result of this method because it returns false too often, and then again true, and so on.
We can try to fix the issue or at least report to the end-user what they should do?
Check if Location Services are enabled (using LocationManager.isProviderEnabled())
Check if you have permissions, request them if needed (docs)
I filter false positives of locationAvailability.isLocationAvailable() by calling this piece of code, which checks if location service is enabled.
fun isLocationEnabled(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
lm.isLocationEnabled
} else {
val mode = Settings.Secure.getInt(
context.contentResolver, Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_OFF
)
mode != Settings.Secure.LOCATION_MODE_OFF
}
}
override fun onLocationAvailability(p0: LocationAvailability?) {
if (isLocationEnabled().not()) {
locationUpdateSubject.failed(LocationAvailabilityError)
}
}
as Google docs says:
When isLocationAvailable() returns false you can assume that location
will not be returned in onLocationResult(LocationResult) until
something changes in the device's settings or environment.
So you can assume that location may be not available not only because of disabled location settings, but because of signal strength, or maybe satellites are not visible or something else, it just indicates that you will not receive location updates until something changes. You can show user notification about it with something like "We can't get your location, try enable location settings"
I've faced same issue and same time I saw that location services has been enabled on the device and my ap had allowed permissions from user.
I still saw
com.google.android.gms.location.LocationCallback#onLocationAvailability LocationAvailability[isLocationAvailable: false]
The issue was that there is additional preference inside Location item on the phone. We have to choose Battery saving/High accuracy option. Please check out the screenshot provided:
To be clear: This is an activity in my Android application that is meant to pull the coordinates for the users location using the GPS_PROVIDER. The Activity contains a button which, when pressed, should initiate a method that obtains the coordinate data. The problem is that the application crashes when there is no previously known location information (ie if the phone was recently reset). If I open up the Maps application (for example) and pinpoint my location, then re-open my own application and run this method, it works as intended. My question is why is this crashing and/or how can I prevent this crash from occurring? Help is appreciated, thanks.
This method is run when the button is pressed - and an intent response is generated back to the calling activity when the coordinates are properly found:
protected void getCurrentLocation() {
Location location = null;
try {
location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
} catch (IllegalArgumentException iae) { }
if (location != null) {
longV = location.getLongitude();
latV = location.getLatitude();
response(longV, latV);
} else {
getCurrentLocation();
}
I'm guessing that you are receiving a StackOverflowException, because if location is null you call the exact same function creating an indefinite loop...
If there is no last know location, you need to request a new location. (getLastKnownLocation() will not change on its own no matter how many times you call it.)