The fused Location Provider will only maintain background location if at least one client is connected to it. Now just turning on the location service will not guarantee to store the last known location.
Once the first client connects, it will immediately try to get a location. If your activity is the first client to connect and getLastLocation() is invoked right away in onConnected(), that might not be enough time for the first location to arrive..
I suggest you to launch the Maps app first, so that there is at least some confirmed location, and then test your app.
Answer from Amit K. Saha on Stack OverflowThe fused Location Provider will only maintain background location if at least one client is connected to it. Now just turning on the location service will not guarantee to store the last known location.
Once the first client connects, it will immediately try to get a location. If your activity is the first client to connect and getLastLocation() is invoked right away in onConnected(), that might not be enough time for the first location to arrive..
I suggest you to launch the Maps app first, so that there is at least some confirmed location, and then test your app.
As in this post said, The fused Location Provider will only maintain background location if at least one client is connected to it.
But we can skip the process of launching the Google Maps app to get last location by following way.
What we need to do is
- We have to request location update from FusedLocationProviderClient
- Then we can get the last location from FusedLocationProviderClient, it wouldn't be null.
Request Location
LocationRequest mLocationRequest = LocationRequest.create();
mLocationRequest.setInterval(60000);
mLocationRequest.setFastestInterval(5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationCallback mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult == null) {
return;
}
for (Location location : locationResult.getLocations()) {
if (location != null) {
//TODO: UI updates.
}
}
}
};
LocationServices.getFusedLocationProviderClient(context).requestLocationUpdates(mLocationRequest, mLocationCallback, null);
Get last location
LocationServices.getFusedLocationProviderClient(context).getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
//TODO: UI updates.
}
});
For best result requestLocationUpdates in onStart() of the Activity, and then you can get last location.
There's nothing to fix, this is expected behavior. getLastLocation returns the last location IF THE SYSTEM KNOWS IT. It almost never knows it. So it will almost always return null. If you want to get an assured location, use requestLocationUpdates. But you can never rely on getLastLocation working, and even when it does the data may be old. It should really only be used in very limited circumstances.
The getLastLocation() returns null if the Android System doesn't have a knowledge about its last location.To provide last location to the System you can try one of the following things:
Before opening your app, open Google Maps and let the device get the current location.After that you can return to your app.
The
Locationservice inAndroidprovide 3 modes.Check-up the High Accuracy mode. Go to Setting -> Location -> High Accuracy Mode.
I have faced this isue as well especially when i first install my app on a device. there are two things that you can try out that i think will help:
one is trying to open the google maps app and waiting for it to get a fix and then go on and open your app. this should provide your fused location provider client a last location that it can look up. check this answer for reference
the second is going a step further and requesting location updates using the provider. If you only need a single location fix you can then go on and deregister it once you get the first one. I am doing this for my app and usually last location is only null the first time i use the app after installation. after that it usually works. this is probably what you should do anyway as you cant expect your users to turn on google maps before using your app.
and just to be safe, be aware that fusedlocationproviderclient sometimes stops working or behaves weirdly when you have pending google services updates on your device. so go ahead and make sure your device is up to date in this regard.
lastLocation can be null for several reasons. They are listed here https://developer.android.com/training/location/retrieve-current#last-known
I found a solution, this is what happens, when the location is null it means the location cache was cleared, this happens when turning off GPS, so when I was turning it on there was no last location to get, what I did was this:
CopycheckLocationSettings(callingActivity, turnOnGpsRequestCode, callback) {
// Location settings successful
mFusedLocationProviderClient!!.lastLocation
.addOnSuccessListener(callingActivity) {
location ->
if (location == null || location.accuracy > 100) {
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
stopLocationUpdates()
if (locationResult != null && locationResult.locations.isNotEmpty()) {
val newLocation = locationResult.locations[0]
callback.onCallback(Status.SUCCESS, newLocation)
} else {
callback.onCallback(Status.ERROR_LOCATION, null)
}
}
}
mFusedLocationProviderClient!!.requestLocationUpdates(getLocationRequest(),
mLocationCallback, null)
} else {
callback.onCallback(Status.SUCCESS, location)
}
}
.addOnFailureListener {
callback.onCallback(Status.ERROR_UNKNOWN, null)
}
}
When the location is null, start requesting locations using a callback and
mFusedLocationProviderClient!!.requestLocationUpdates(getLocationRequest(), mLocationCallback, null)
Then when the callback is called, a new location is got and it starts getting location again.
Sometimes it happens that when you turn on the GPS, the location is not null but the accuracy is bad, so I also check if location accuracy is good enough (For me good enough is 100 meters)
You can use getLocationAvailability() method on your FusedLocationPrivedClient object and if it returns true then only use getLastLocation() method otherwise use requestLocationUpdates() method like this :
CopyFusedLocationProviderClient fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(InitActivity.this);
if (ActivityCompat.checkSelfPermission(this.getApplicationContext(),
android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
fusedLocationProviderClient.getLocationAvailability().addOnSuccessListener(new OnSuccessListener<LocationAvailability>() {
@Override
public void onSuccess(LocationAvailability locationAvailability) {
Log.d(TAG, "onSuccess: locationAvailability.isLocationAvailable " + locationAvailability.isLocationAvailable());
if (locationAvailability.isLocationAvailable()) {
if (ActivityCompat.checkSelfPermission(InitActivity.this.getApplicationContext(),
android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
Task<Location> locationTask = fusedLocationProviderClient.getLastLocation();
locationTask.addOnCompleteListener(new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
Location location = task.getResult();
}
});
} else {
requestLocationPermissions ();
}
} else {
fusedLocationProviderClient.requestLocationUpdates(locationRequest, pendingIntent);
}
}
});
} else {
requestLocationPermissions ();
}
First of all, if you're using the emulator you have to set the position manually doing:
telnet localhost 5554
when connected run the command
geo fix <longitude> <latitude>
as decribed here Using the emulator #geo or here how-to-emulate-gps-location-in-the-android-emulator. I suggest you to do so at soon as the emulator is booting and obviously before you're app start.
Second thing, your FusedLocation isn't implementing LocationListener as it is in the example you have posted. You have to implement that interface and then implement the method onLocationChanged inside your FusedLocation. Anyway, I think that you are trying to load the location as soon as the app is started, right? Well the example you posted doesn't work in this way but it's the user that ask for location through a visible botton. If you want to load the location at the beginning you can modify the example code like this:
the FusedLocationService is your FusedLocation class
import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.FusedLocationProviderApi;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FusedLocationService implements
LocationListener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private static final String TAG = "FusedLocationService";
private static final long INTERVAL = 1000 * 30;
private static final long FASTEST_INTERVAL = 1000 * 5;
private static final long ONE_MIN = 1000 * 60;
private static final long REFRESH_TIME = ONE_MIN * 5;
private static final float MINIMUM_ACCURACY = 50.0f;
Activity locationActivity;
private LocationRequest locationRequest;
private GoogleApiClient googleApiClient;
private Location location;
private FusedLocationProviderApi fusedLocationProviderApi = LocationServices.FusedLocationApi;
FusedLocationReceiver locationReceiver = null;
public FusedLocationService(Activity locationActivity, FusedLocationReceiver locationReceiver) {
this.locationReceiver = locationReceiver;
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(INTERVAL);
locationRequest.setFastestInterval(FASTEST_INTERVAL);
this.locationActivity = locationActivity;
googleApiClient = new GoogleApiClient.Builder(locationActivity)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
if (googleApiClient != null) {
googleApiClient.connect();
}
}
@Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "I'm connected now");
Location currentLocation = fusedLocationProviderApi.getLastLocation(googleApiClient);
if (currentLocation != null && currentLocation.getTime() > REFRESH_TIME) {
location = currentLocation;
} else {
fusedLocationProviderApi.requestLocationUpdates(googleApiClient, locationRequest, this);
// Schedule a Thread to unregister location listeners
Executors.newScheduledThreadPool(1).schedule(new Runnable() {
@Override
public void run() {
fusedLocationProviderApi.removeLocationUpdates(googleApiClient,
FusedLocationService.this);
}
}, ONE_MIN, TimeUnit.MILLISECONDS);
}
}
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, "Location is changed!");
//if the existing location is empty or
//the current location accuracy is greater than existing accuracy
//then store the current location
if (null == this.location || location.getAccuracy() < this.location.getAccuracy()) {
this.location = location;
// let's inform my client class through the receiver
locationReceiver.onLocationChanged();
//if the accuracy is not better, remove all location updates for this listener
if (this.location.getAccuracy() < MINIMUM_ACCURACY) {
fusedLocationProviderApi.removeLocationUpdates(googleApiClient, this);
}
}
}
public Location getLocation() {
return this.location;
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
}
This is the receiver
public abstract class FusedLocationReceiver {
public abstract void onLocationChanged();
}
and this is the activity
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
public class MainActivity extends Activity {
private static final String TAG = "MyActivity";
Button btnFusedLocation;
TextView tvLocation;
FusedLocationService fusedLocationService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//show error dialog if GoolglePlayServices not available
if (!isGooglePlayServicesAvailable()) {
finish();
}
setContentView(R.layout.activity_main);
tvLocation = (TextView) findViewById(R.id.tvLocation);
fusedLocationService = new FusedLocationService(this, new FusedLocationReceiver(){
@Override
public void onLocationChanged() {
Log.i(TAG, "I'm the receiver, let's do my job!");
updateUI();
}
});
btnFusedLocation = (Button) findViewById(R.id.btnGPSShowLocation);
btnFusedLocation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
updateUI();
}
});
}
private void updateUI() {
Location location = fusedLocationService.getLocation();
String locationResult = "";
if (null != location) {
Log.i(TAG, location.toString());
double latitude = location.getLatitude();
double longitude = location.getLongitude();
float accuracy = location.getAccuracy();
double elapsedTimeSecs = (double) location.getElapsedRealtimeNanos()
/ 1000000000.0;
String provider = location.getProvider();
double altitude = location.getAltitude();
locationResult = "Latitude: " + latitude + "\n" +
"Longitude: " + longitude + "\n" +
"Altitude: " + altitude + "\n" +
"Accuracy: " + accuracy + "\n" +
"Elapsed Time: " + elapsedTimeSecs + " secs" + "\n" +
"Provider: " + provider + "\n";
} else {
Log.i(TAG, "Location Not Available!");
locationResult = "Location Not Available!";
}
tvLocation.setText(locationResult);
}
private boolean isGooglePlayServicesAvailable() {
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (ConnectionResult.SUCCESS == status) {
return true;
} else {
GooglePlayServicesUtil.getErrorDialog(status, this, 0).show();
return false;
}
}
}
Essentially when you instantiate a FusedLocationService you give it an object (the receiver) that will be called by the FusedLocationService when the location is ready. Warning: this will give you the location only once.
You please check your location service.
It may be off so turn it on and then test it.
You can also put condition for GPS is enabled or not like:
//exceptions will be thrown if provider is not permitted.
try {
gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if(gpsEnabled) {
// Your Code....
}
} catch (Exception ex) {
Log.e("TAG", "GPS Error : " + ex.getLocalizedMessage());
}
Thanks.