Android get high resolution location info on button press - android

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());
}
}

Related

How to make sure that getLastLocation().isSuccessful()

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.

fusedLocationClient.getLastLocation always returns NULL

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.

Should I programmatically turn on Wifi and Bluetooth scanning for better location accuracy?

I am currently making an app that tracks user movements. Basically, it requires the user to move for a given distance in meter. And since the user can move indoor, I need high accuracy badly. I am using FusedLocationProviderClient to do my job.
When the app starts, it will check the location settings to see if they are all satisfied. And if they are not, it will prompt a dialog in which allows the user to just only tap OK and then all location settings will be right set.
I have to use LocationSettingsRequest.Builder and ResolvableApiException class to do that.
The LocationSettingsRequest.Builder class gets a LocationRequest object as an input and then checks if the location settings in the device are satisfied with the given LocationRequest object. Doing so will throw a ResolvableApiException object if the location settings of the device are not satisfied. And that ResolvableApiException object can prompt a dialog allowing the user to make these settings right. More details at Change location settings on developer.android.com
The LocationSettingsRequest.Builder need a LocationRequest as an input, so this is my LocationRequest:
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(2000);
And my code in my MainActivity to check the location settings:
public void checkLocationSettings(){
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locationRequest);
SettingsClient settingsClient = LocationServices.getSettingsClient(this);
Task<LocationSettingsResponse> task = settingsClient.checkLocationSettings(builder.build());
task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
#Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
// do works
}
});
task.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
try {
ResolvableApiException resolvable = (ResolvableApiException) e;
resolvable.startResolutionForResult(MainActivity.this, 1);
} catch (IntentSender.SendIntentException e1) {
e1.printStackTrace();
}
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
switch (requestCode){
case 1:
if(resultCode== RESULT_OK){
// do works
}
else{
// I will automatically terminate the app in this case
}
break;
}
}
I am using Samsung Galaxy J7 Pro with Android OS version 8.1.0. In Location setting, there are two options that are Locating method and Improve accuracy. Locating method has 3 options that are High accuracy, Battery saving and Phone only. Improve accuracy has 2 options that are Wi-Fi scanning and Bluetooth scanning.
I tried turning off the device Location, turning off all 2 options in Improve accuracy and I set Locating method to Phone only. Then I opened my app, there was a dialog like this appeared:
Forget about the situation when the user click NO THANKS. When the user click OK, I can see the Location service has been activated. The Locating method also be set to High accuracy. But there is no changes inside Improve accuracy options.
This makes me wonder. I wonder if Improve accuracy becomes useless when Locating method is being High accuracy. Or it is not useless at all and I have to programmatically turn them on too.
I'm sorry I can't post all of my code because it's quite complex and redundant for my problem. But if you want, I can create a whole new project to demonstrate the problem. And I don't know if this is a good place to ask something like this too. If it's not, could you show me one? I appreciate any idea or solution.
EDIT: So I have created a new simple project to demonstrate this problem. You can check it here.
As per scanning settings description, wifi and bluetooth scanning can work even when wifi and bluetooth are turned off by the user. So manually switching on bluetooth/wifi is not necessary.
As per documentation, setting builder.setNeedBle(true); should prompt the user to enable bluetooth scanning if the bluetooth is disabled by the user.
Turns out ResolvableApiException can actually turn on Wi-Fi scanning inside Improve accuracy option. But only when the Wifi is not turned on. In that case, the dialog will look like this:
And when the user click OK, it does turn on Wi-Fi scanning in Improve accuracy option. Note that it doesn't turn on Wifi like you will normally do to surf the net or something like that. It just turns on the feature called Wi-Fi scanning
As of the Bluetooth scanning feature, I don't know why ResolvableApiException doesn't turn it on. But since it does deal with the Wi-Fi scanning, I assume that the ResolvableApiException is smart enough to decide what it has to do. So, I think I don't need to worry about the accuracy anymore.

Android LocationManager requestLocationUpdates changed?

My app is working in Ice Cream Sandwich perfectly well, but now I tried it on KitKat and faced some problems.
The app is kind of server I'm running in my old phone and it provides location when requested. In ICS when the location is requested the GPS icon starts blinking and soon the app receives location update and sends it forward. But now with KitKat the GPS icon does not start blinking when location is requested. The app gives 60s time for finding the GPS location, but usually the GPS isn't even activated during this time. Still now and then the GPS suddenly activates itself (during the 60s) and the location is provided to my app.
Why the GPS doesn't get activated even my app requests location? As said, my app works with ICS without problems. And I do have required permissions set in my manifest.
public variables:
public static LocationManager mlocManager = null;
public static LocationListener mlocListener_fast = null;
onCreate:
mlocListener_fast = new MyLocationListener();
mlocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Handling user requested command (location request)
mlocManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 0, 0, mlocListener_fast);
What should I do differently to get the GPS location instantly without waiting for sudden location updates/GPS activation, probably triggered by Android or some other app?? Unfortunately I don't have any other Android device I could try this.
EDIT:
It seems that if the app does not request location at startup, then the location request works every time when requested time after time. But if the location was requested (and received) on startup, then the location request does not work anymore. What can cause that? I use exactly the same line (the same location mgr and the same location listener) for location request on startup and later if requested.
Even if the location listener used in startup is different than the one used later, the location request does not work anymore. Tried even initialize the location mngr again just before requesting the location again and it did not help. What's with this??
EDIT2:
It just seems that with KitKat it's not possible to request multiple location requests. I used to have several location listeners for different purposes. For example one for updating location once per hour and another for getting location instantly (user requested update). Now it seems that if I have the 1/60min location listener running as normal, then KitKat location manager fails to handle the instant location requests. Have anyone faced this issue? Would be good to know which Android versions have this issue.
Workaround for this issue is to use only one LocationManager and one LocationListener. If your app has needs for different kind of simultaneous location requests (with different parameters), then you need to implement a "location request handler" which decides which parameters should be used for the location request i.e. which parameters have the tightest requirements for location.
Here is a simple example code that explains the idea of "location request handler":
class LR {
long lock_min_time; // defined in set_lock_lr before using
float lock_min_dist;
boolean lock_active = false;
long idle_min_time = 3600000; // 1 per hour
float idle_min_dist = 200;
boolean idle_active = true;
long fast_min_time = 0;
float fast_min_dist = 0;
boolean fast_active = false;
//constructor
public LR()
{}
public void set_lock_lr(long min_time, float min_dist, boolean active)
{
lock_active = active;
lock_min_dist = min_dist;
lock_min_time = min_time;
System.out.println("LR lock set: "+min_time+", "+min_dist+", "+active);
update_location_request();
}
public void set_idle_lr(boolean active)
{
idle_active = active;
System.out.println("LR idle set: "+active);
update_location_request();
}
public void set_fast_lr(boolean active)
{
fast_active = active;
System.out.println("LR fast set: "+active);
update_location_request();
}
private void update_location_request()
{
// Remove current location request
mlocManager_basic.removeUpdates(mlocListener_basic);
if(fast_active)
{
mlocManager_basic.requestLocationUpdates(LocationManager.GPS_PROVIDER, fast_min_time, fast_min_dist, mlocListener_basic);
System.out.println("LR: fast_active");
}
else if(lock_active)
{
mlocManager_basic.requestLocationUpdates(LocationManager.GPS_PROVIDER, lock_min_time, lock_min_dist, mlocListener_basic);
System.out.println("LR: lock_active");
}
else if(idle_active) // only idle updates
{
mlocManager_basic.requestLocationUpdates(LocationManager.GPS_PROVIDER, idle_min_time, idle_min_dist, mlocListener_basic);
System.out.println("LR: idle_active");
}
}
}

Why Location client gives very far location from my current location.?

I am developing a location based app which have the functionality to update user current location in every 1 minutes.
I am using bellow to code for requesting location updates:
private LocationRequest mLocationRequest;
private static LocationClient mLocationClient;
mLocationRequest = LocationRequest.create();
mLocationRequest.setInterval(60000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setFastestInterval(60000);
mLocationClient = new LocationClient(this, this, this);
if (servicesConnected()) {
mLocationClient.connect();
}
servicesConnected() is user defined method which returns true if Google play services is available otherwise returns false
and my overriden method like this:
#Override
public void onConnected(Bundle connectionHint) {
try {
mLocationClient.requestLocationUpdates(mLocationRequest, this);
} catch (IllegalStateException e) {
// TODO: handle exception
}
}
#Override
public void onLocationChanged(Location location) {
// logic to store location data
}
But I found location updates like bellow figure while my GPS is ON :
Please suggest what should I do to overcome unwanted location updates.
Here's some info from OwnTracks https://github.com/owntracks/android/issues/66
From my research the only thing you can do is filter out "bad" locations like this:
#Override
public void onLocationChanged(Location location) {
if(location.getAccuracy() < 210.0) {
// use provided location
}
}
It doesn't appear that there is any way to prevent these updates from being requested using the Fused provider (other than stopping updates or increasing the interval when you have a location you are satisfied with). There's only so much "filtering" that can be done by the GPS itself and those options are the constants inside LocationRequest that you already know about. I believe your issue is hardware related or has to do with the location you are getting updates from (I'm basing this assumption on the OwnTracks data). Google could theoretically offer more advanced Criteria like it used to do with LocationManager, but I believe that would basically do the same thing as my example, except under the hood (i.e. the GPS would still do the work of getting the location and then discard it AFTER it knows the accuracy isn't high enough).
If you want to make it do less work, your best options are increasing the interval or simply stopping updates when you no longer need new locations. For example, if you have a decent location, maybe you raise the interval and keep that location longer. But that depends on what your app is trying to do with the data.

Categories

Resources