FusedLocation is not always accurate - java

I'm developing an app for my work. It's main goal is to verify if workers are in the place they're supposed to be. So, they Check-in at a pint of sale, and immediately a tracking Job is fired, getting user's location every hour. When they leave the place, the job is stopped.
I'm using JobScheduler, as I read this is the best way to handle this, and it's working propertly, so this is fine. The problem is, location is not always accurate, and this is a MUST, because the bosses of the workers need to verify that they are at their point of sale.
There have been many cases when a user is inside the POS, but the app shows like he/she was some meters away from the allowed area. (We are using a 30m radius as the "allowed area"). So, if the app is not showing reliable information, our client won't trust our app anymore.
This is my code (I'm showing just location stuff) I took as reference this google sample. :
//Package and imports...
public class TrackingJob extends JobService{
// Some variables...
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 5000;
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2;
private LocationCallback mLocationCallback;
private Location mLastLocation;
private LocationRequest mLocationRequest;
private LocationSettingsRequest mLocationSettingsRequest;
private FusedLocationProviderClient mFusedLocationProviderClient;
#Override
public boolean onStartJob(JobParameters jobParameters) {
createLocationCallback(jobParameters);
createLocationRequest();
buildLocationSettingsRequest();
startLocationServices();
return true; // Async stuff will be done, so true is returned
}
#Override
public boolean onStopJob(JobParameters jobParameters) { return false; }
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
private void createLocationCallback(final JobParameters jobParameters) {
mLocationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
mLastLocation = locationResult.getLastLocation();
// Save data locally, and then, upload it to server
saveLocalData(jobParameters.getExtras(), true);
syncData(jobParameters);
// We stop locationProvider here, because we got a location already
mFusedLocationProviderClient.removeLocationUpdates(mLocationCallback);
}
};
}
private void buildLocationSettingsRequest(){
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
}
#SuppressLint("MissingPermission") // Permissions WILL BE accepted before the job is called
private void startLocationServices() {
SettingsClient mSettingsClient = LocationServices.getSettingsClient(this);
mSettingsClient.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() {
#Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
mFusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(TrackingJob.this);
// We ask for location updates
mFusedLocationProviderClient.requestLocationUpdates(
mLocationRequest,
mLocationCallback,
Looper.myLooper()
);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// If problem can be solved by user, we send a push noty
}
});
}
private void saveLocalData(PersistableBundle bundle, boolean gps){
// We save data to REALM
}
private void syncData(final JobParameters jobParameters){
// We get data to sync from REALM
// If data was succesfully synced, we delete it from REALM
// We stop the job
jobFinished(jobParameters, false);
}
}
Am I doing something wrong? Is there a better and more accurate way of getting user's location? Is this a "phone-related" problem?
Before using this new FusedLocationProvider, I was using GoogleApiClient connectionCallbacks and LocationListeners, and accuracy was a problem too.

Related

How to periodically update the location to Firebase Realtime database?

I am creating a realtime public transport tracking system using Android Studio and Firebase. I have created a toggle button which the user toggle on, the location will start tracking.
The problem I have faced is the location data only send one time to the database. But for the apps, the location is tracking normally.
What I need is every time the location update have to update into the database. Is that mistake or error in my code?
This is the location callback function:
//Location Callback
locationCallback = new LocationCallback(){
#Override
public void onLocationResult(LocationResult locationResult) {
if(locationResult == null) {
return;
}
Location driverLocation = locationResult.getLastLocation();
Toast.makeText(DriverDetail.this, driverLocation.getLatitude()+", "+driverLocation.getLongitude()
, Toast.LENGTH_SHORT).show();
Log.d("Location Update: ", "onLocationResult: Location is: "
+driverLocation.getLatitude()+", "+driverLocation.getLongitude());
}
};
This is the code for the Toggle button
locate_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View button) {
if (locate_btn.isChecked()) {
// The toggle is enabled
Toast.makeText(DriverDetail.this, "Start tracking location", Toast.LENGTH_SHORT).show();
final String value_lat = String.valueOf(driverLocation.getLatitude());
final String value_lng = String.valueOf(driverLocation.getLongitude());
getLocationUpdates();
ref.child(firebaseUser.getUid()).child("Latitude").setValue(value_lat);
ref.child(firebaseUser.getUid()).child("Longitude").setValue(value_lng);
}
else {
// The toggle is disabled
fusedLocationProviderClient.removeLocationUpdates(locationCallback);
Toast.makeText(DriverDetail.this, "Stop tracking location", Toast.LENGTH_SHORT).show();
}
}
});
This is the location request that I have set.
//Update Location
private void getLocationUpdates(){
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(3000);
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
}
Your code is right now writing the location data to Firebase when the user toggles the button.
What you instead want is to write the location continuously, once the button has been toggled. This means that the writing should happen (or be triggered from) inside onLocationResult:
locationCallback = new LocationCallback(){
#Override
public void onLocationResult(LocationResult locationResult) {
if(locationResult == null) {
return;
}
Location driverLocation = locationResult.getLastLocation();
if (true) { // TODO: check if button is toggled
final String value_lat = String.valueOf(driverLocation.getLatitude());
final String value_lng = String.valueOf(driverLocation.getLongitude());
ref.child(firebaseUser.getUid()).child("Latitude").setValue(value_lat);
ref.child(firebaseUser.getUid()).child("Longitude").setValue(value_lng);
}
}
};
In the if (true) statement you might want to check if the button is toggled. But since I noticed that you only register for location updates when the user toggles the button, that check might not even be needed.

How to get the absolute current location of the device in android java

Perhaps I am misunderstanding, but all the articles i've found for locationmanager and locationlistener in android refer to the onLocationChanged() method.
I want to get the current position of the user, not when it's changed. Am I using the right method? The locationchanged listener is working, and in the emulator runs the method when I change the location.
My app workflow is this:
-> App gets request from server for location
-> locationservice starts and stays on for 5 seconds to get the location
-> locationservice saves the location to preferences
-> locationservice stops
-> messaging service sends location to server
is a location listener the right method? will it still work if the user does not change location?
here's my location service:
public class MyLocationService extends Service {
public LocationManager locationManager;
public LocationListener mLocationListener;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#SuppressLint("MissingPermission")
public int onStartCommand(Intent intent, int flags, int startId){
locationManager = (LocationManager)getSystemService(LOCATION_SERVICE);
mLocationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MyLocationService.this).edit();
editor.putString("latitude", Double.toString(location.getLatitude()));
editor.putString("longitude", Double.toString(location.getLongitude()));
editor.commit();
System.out.println("Location got changed");
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
};
try {
locationManager.requestLocationUpdates(locationManager.getBestProvider(new Criteria(), true), 10000, 0, mLocationListener);
} catch (Exception e){
e.printStackTrace();
}
return START_STICKY;
}
}
but the location change is only registering when I do it as the service is open. How can I run that every time the service runs?
You will get the user's current position the first time LocationManager triggers LocationListener::onLocationChanged.
If you are interested only in the current location you can set up the LocationListener to receive the first location update and then stop.
public void onLocationChanged(Location location) {
// location gotten... store it somewhere
// and stop the location manager
locationManager.removeUpdates(this.locationListener);
}
Still, LocationListener::onLocationChanged will trigger depending on the criteria you have set to your LocationManager.
For instance:
this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this.locationListener);
will not be limited by any criteria, it will continuously request location updates.
is a location listener the right method? will it still work if the user does not change location?
Yes

FusedLocationProviderClient.removeLocationUpdates always returns failure

I have an activity that extends a base class called LocationAwareActivity all this LocationAwareActivity activity does is creates a location service client
LocationServices.getFusedLocationProviderClient and listens to
location updates.
Source for this activity is here
https://github.com/snijsure/MultiActivity/blob/master/app/src/main/java/com/example/subodhnijsure/multiactivity/LocationAwareActivity.java
And when activity is destroyed it calls removeLocationUpdates . What I am finding is
removeLocationUpdate returns a task that always returns not-successful
More concerning is because location activities is not removed, the activity is not getting being garbage collected.
- So if I start the any activity that inherits from LocationAwareActivity that activity always stays on heap.
So the question is what is the correct way to stop receiving location updates thus allowing activity to be garbage collected.
Entire source for this project can be accessed here - https://github.com/snijsure/MultiActivity
In removeLocationUpdates you should pass locationCallback, current implementation is wrong.
Still, there is chance of memory leak somewhere else. You should try integrating Leakcanary in your app and it can give you reference tree and will tell you which field or listener is causing this memory leak.
You can refer one of my only blog post here
public void stopLocationUpdates() {
if (locationProviderClient != null) {
try {
final Task<Void> voidTask = locationProviderClient.removeLocationUpdates(locationCallback);
if (voidTask.isSuccessful()) {
Log.d(TAG,"StopLocation updates successful! ");
} else {
Log.d(TAG,"StopLocation updates unsuccessful! " + voidTask.toString());
}
}
catch (SecurityException exp) {
Log.d(TAG, " Security exception while removeLocationUpdates");
}
}
}
Hi #Subodh Nijsure Please check below code and paste into your code and after checked it:
final Task<Void> voidTask = locationProviderClient.removeLocationUpdates(locationCallback);
voidTask.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Log.e(TAG, "addOnCompleteListener: "+task.isComplete());
}
});
voidTask.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.e(TAG, "addOnSuccessListener: " );
}
});
voidTask.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "addOnFailureListener: ");
}
});
I think voidTask.isSuccessful() this method is not working when you put this listener at that time it working fine and i also see into memory it's release all memory when come to previous Activity.
And when you are redirecting to any activity then please stopLocationUpdates() called once into onPause() and remove from other method like onDestroy(),onStop() because it stop once so why should we call multiple time.
Hope this helps you.
By looking at the code in the repository I discovered some issues in your design that maybe cause the leaking of your Activity.
1) You are using two different LocationCallbacks. One in the start and one in the stop method, but you should actually use the same. So one time instantiating it would be sufficient and would lead probably also to a successful result of your Task when removing the LocationCallback.
2) Since your instantiating the LocationCallback twice with an Anonymous Class you are keeping a non-static reference of an inner class even if you finish the containing class and this causes your Memory Leak. You can read more about this here.
3) IMHO it is better to use a separate manager class for handling your location requests than abstracting an Activity.
That said here is my...
Solution
GpsManager.java
public class GpsManager extends LocationCallback {
private FusedLocationProviderClient client;
private Callback callback;
public interface Callback {
void onLocationResult(LocationResult locationResult);
}
public boolean start(Context context, Callback callback) {
this.callback = callback;
client = LocationServices.getFusedLocationProviderClient(context);
if (!checkLocationPermission(context)) return false;
client.requestLocationUpdates(getLocationRequest(), this, null);
return true;
}
public void stop() {
client.removeLocationUpdates(this);
}
#Override
public void onLocationResult(LocationResult locationResult) {
callback.onLocationResult(locationResult);
}
private boolean checkLocationPermission(Context context) {
int permissionCheck = ContextCompat.checkSelfPermission(
context, android.Manifest.permission.ACCESS_FINE_LOCATION);
return permissionCheck == PackageManager.PERMISSION_GRANTED;
}
private LocationRequest getLocationRequest() {
return LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setInterval(30_000L)
.setFastestInterval(20_000L);
}
}
and calling this from your Activity like this
YourActivity.java
public class MapsActivity extends AppCompatActivity implements GpsManager.Callback {
private static final int PERMISSION_REQUEST_FINE_LOCATION = 1;
private GpsManager mGpsManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mGpsManager = new GpsManager(getApplicationContext(), this);
// check if user gave permissions, otherwise ask via dialog
if (!checkPermission()) {
getLocationPermissions();
return;
}
mGpsManager.start();
...
}
#Override
protected void onStop() {
super.onStop();
mGpsManager.stop();
}
#Override
public void onLocationResult(LocationResult locationResult) {
// do something with the locationResult
}
// CHECK PERMISSIONS PART
private boolean checkPermission() {
return isGranted(ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION)) &&
isGranted(ActivityCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION));
}
#TargetApi(Build.VERSION_CODES.M)
private void getLocationPermissions() {
requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSION_REQUEST_FINE_LOCATION);
}
#Override
public void onRequestPermissionsResult(int code, #Nullable String permissions[], #Nullable int[] results) {
switch (code) {
case PERMISSION_REQUEST_FINE_LOCATION:
if (isPermissionGranted(results)) {
getLocationRequest();
}
}
}
private boolean isPermissionGranted(int[] results) {
return results != null && results.length > 0 && isGranted(results[0]);
}
private boolean isGranted(int permission) {
return permission == PackageManager.PERMISSION_GRANTED;
}
}
This is just a guess because I didn't try your code but the solution should help you anyways. Please correct me if I'm wrong ;)
The reason why the Task object returns false is in your stopLocationUpdates method, you are again creating a local **LocationCallback** reference and then using this reference to as an argument in locationProviderClient.removeLocationUpdates(cL);
where your local LocationCallBack is never present in the locationProviderClient
So what you have to do is , instead of creating another LocationCallBack object ,you have to pass the same global object which you are instantiating in your startLocationUpdates method
your code should be like this
final Task<Void> voidTask = locationProviderClient.removeLocationUpdates(locationCallback);

Android - getting current location through Google Map

I am developing an app related to Google map. I have done following steps successfully.
Created API key to access Google Map
Added Google Play Services Library in my app
Added required permissions
Added map in my activity with SupportMapFragment
Added a separate class MyMap.java to manipulate the map
Passed tow parameters to this class - Context of main activity and object of GoogleMap
Turned Wi-Fi and GPS on and ran the app
After this I am getting map with nice look and controls.
MyMap.java
public class MyMap implements ConnectionCallbacks, OnConnectionFailedListener {
private Context context;
private GoogleMap map;
private GoogleApiClient client = null;
public MyMap(Context context, GoogleMap map) {
this.context = context;
this.map = map;
client = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).build();
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
// TODO Auto-generated method stub
}
#Override
public void onConnected(Bundle arg0) {
Toast.makeText(context, "Connected", 1).show();
Location mLastLocation = LocationServices.FusedLocationApi
.getLastLocation(client);
if (mLastLocation != null) {
Toast.makeText(
context,
String.valueOf(mLastLocation.getLatitude()) + ","
+ String.valueOf(mLastLocation.getLongitude()), 1)
.show();
}
}
#Override
public void onConnectionSuspended(int arg0) {
// TODO Auto-generated method stub
}
}
Problem
In the above class I want to toast the current location. But it is not toasting anything. At least I need to see a toast saying "connected" on onConnected
event. Is there something wrong in my implementation?
Thanks in advance.
You seemingly never connect your client so it would be a real suprise if onConnected was called :)
You create your client with
client = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).build();
but for the client to do something you have to add:
client.connect();
getLastLocation() is going to give location only once. To get periodic location updates, you need to override onLocationChanged() method. You can get this Link
Best way that I found is simple implement you activity like so:
public class MapActivity extends Activity implements GoogleMap.OnMyLocationChangeListener
and override method
#Override
public void onMyLocationChange(Location location) {
mMap.addMarker(new MarkerOptions().position(location).icon(
BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)));
}
And don't forget about mMap.setMyLocationEnabled(true); and mMap.setOnMyLocationChangeListener(this); in map init method
That's all!
Also, you can check is map available like here:
public boolean checkMapsAvailable() {
int isAvailable = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (isAvailable == ConnectionResult.SUCCESS) {
return true;
} else if (GooglePlayServicesUtil.isUserRecoverableError(isAvailable)) {
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(isAvailable, this, 9001);
dialog.show();
} else {
Constants.showToast(Constants.ALERT_GOOGLEPLAY_CONNECTION);
}
return false;
}
Hope this helps.

Wait for LocationClient callback

I am trying to write simple method to get location via Google Play Services. Something like this (yeah, it probably has some other problems..):
Location getLastLocation() {
final Location location = new Location("");
locationClient.registerConnectionCallbacks(new GooglePlayServicesClient.ConnectionCallbacks() {
#Override
public void onConnected(final Bundle connectionHint) {
location.set(locationClient.getLastLocation());
locationClient.unregisterConnectionCallbacks(this);
locationClient.disconnect();
}
#Override
public void onDisconnected() {};
});
if (!locationClient.isConnecting() || !locationClient.isConnected()) {
locationClient.connect();
}
return location;
}
obviously, it will return empty Location. So, how can I make it wait for the callback to finish? Of course, without blocking main thread....

Categories

Resources