An Android location “Fused Location Provider API” example
As a brief note to self, here’s some Java source code for a little Android project that kind of strips things down so I can focus on how to get Android location information with the Fused Location Provider API:
/**
*
* AOSP - Android Open Source Project style guide
* ----------------------------------------------
* https://source.android.com/setup/contribute/code-style#follow-field-naming-conventions
*
* `m` Non-public, non-static field names start with m.
* `s` Static field names start with s.
* Other fields start with a lower case letter.
* Public static final fields (constants) are ALL_CAPS_WITH_UNDERSCORES.
*
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MAIN_ACTIVITY";
private GoogleApiClient mGoogleApiClient;
private FusedLocationProviderClient mFusedLocationProviderClient;
private SettingsClient mSettingsClient;
private LocationCallback mLocationCallback;
private LocationRequest mLocationRequest;
private LocationSettingsRequest mLocationSettingsRequest;
private Location mLocation;
private TextView mTextView;
private int mCount = 1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.helloTextView);
// note: in production code you might want to test that the
// GoogleApiClient is available
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
mSettingsClient = LocationServices.getSettingsClient(this);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Log.i(TAG, "ENTERED LocationCallback::onLocationResult");
super.onLocationResult(locationResult);
mLocation = locationResult.getLastLocation();
mTextView.setText("" + mCount + ": " + mLocation.toString());
mCount++;
}
};
// set up the request. note that you can use setNumUpdates(1) and
// setInterval(0) to get one request.
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(10000);
mLocationRequest.setFastestInterval(5000);
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
// request ACCESS_FINE_LOCATION using the Dexter library
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(new PermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
startLocationUpdates();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
// open device settings when the permission is
// denied permanently
openSettings();
}
}
@Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
private void startLocationUpdates() {
mSettingsClient
.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "Success: All location settings are met.");
mFusedLocationProviderClient.requestLocationUpdates(
mLocationRequest,
mLocationCallback,
Looper.myLooper()
);
if (mLocation == null) {
Log.i(TAG, "mLocation WAS NULL");
} else {
//TODO/NOTE - this is never called
mTextView.setText("INITIAL LOC: " + mLocation.toString());
}
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
"location settings ");
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
ResolvableApiException rae = (ResolvableApiException) e;
int requestCheckSettings = 100; //?
rae.startResolutionForResult(MainActivity.this, requestCheckSettings);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be " +
"fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);
Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
}
}});
}
private void openSettings() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
You also need this setting in your AndroidManifest.xml file:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
I thought that permissions line would be enough to be granted location permissions, but most of that other code I threw in there was necessary, at least in an Android emulator. At some point I may try to minimize all that code, but that’s what I have for today.
I pieced that code together from several different sources. From my notes I think these are the sources I used:
- androidhive.info/2012/07/android-gps-location-manager-tutorial/
- github.com/ravi8x/AndroidLocation
- github.com/googlesamples/android-play-location/tree/master/LocationUpdates
(If I missed any other sources that I used, my apologies.)
A Kotlin version of the same code
If you’re interested, here’s a Kotlin version of that same code, including some comments to help explain the Kotlin syntax:
/**
*
* var s: String? = null nullable type
* s?.length safe-call operator
* s?.length ?: 0 elvis operator
* s!!.length force operator
* field!! the not-null assertion operator (!!) converts any value
* to a non-null type and throws an exception if the value is null
*
*/
class MainActivity : AppCompatActivity() {
private var mFusedLocationProviderClient: FusedLocationProviderClient? = null
private var mSettingsClient: SettingsClient? = null
private var mLocationCallback: LocationCallback? = null
private var mLocationRequest: LocationRequest? = null
private var mLocationSettingsRequest: LocationSettingsRequest? = null
private var mLocation: Location? = null
private var mTextView: TextView? = null
private var mCount = 1
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mTextView = findViewById(R.id.helloTextView)
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
mSettingsClient = LocationServices.getSettingsClient(this)
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
Log.i(TAG, "ENTERED LocationCallback::onLocationResult")
super.onLocationResult(locationResult)
mLocation = locationResult!!.lastLocation
mTextView!!.text = "$mCount: $mLocation"
mCount++
}
}
// set up the request. note that you can use setNumUpdates(1) and
// setInterval(0) to get one request.
mLocationRequest = LocationRequest.create()
mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationRequest!!.interval = 10000
mLocationRequest!!.fastestInterval = 5000
val builder = LocationSettingsRequest.Builder()
builder.addLocationRequest(mLocationRequest!!)
mLocationSettingsRequest = builder.build()
// request ACCESS_FINE_LOCATION using the Dexter library
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(object : PermissionListener {
override fun onPermissionGranted(response: PermissionGrantedResponse) {
startLocationUpdates()
}
override fun onPermissionDenied(response: PermissionDeniedResponse) {
if (response.isPermanentlyDenied) {
// open device settings when the permission is
// denied permanently
openSettings()
}
}
override fun onPermissionRationaleShouldBeShown(permission: PermissionRequest, token: PermissionToken) {
token.continuePermissionRequest()
}
}).check()
}
// Kotlin requires this SuppressLint check, where Java did not
@SuppressLint("MissingPermission")
private fun startLocationUpdates() {
mSettingsClient!!
.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(this) {
Log.i(TAG, "All location settings are satisfied.")
mFusedLocationProviderClient!!.requestLocationUpdates(
mLocationRequest,
mLocationCallback!!,
Looper.myLooper()
)
if (mLocation == null) {
Log.i(TAG, "mLocation WAS NULL")
} else {
//TODO/NOTE - this is never called
mTextView!!.text = "INITIAL LOC: " + mLocation!!.toString()
}
}
.addOnFailureListener(this) { e ->
val statusCode = (e as ApiException).statusCode
when (statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " + "location settings ")
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
val rae = e as ResolvableApiException
val requestCheckSettings = 100 //?
rae.startResolutionForResult(this@MainActivity, requestCheckSettings)
} catch (sie: IntentSender.SendIntentException) {
Log.i(TAG, "PendingIntent unable to execute request.")
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
val errorMessage =
"Location settings are inadequate, and cannot be " + "fixed here. Fix in Settings."
Log.e(TAG, errorMessage)
Toast.makeText(this@MainActivity, errorMessage, Toast.LENGTH_LONG).show()
}
}
}
}
private fun openSettings() {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
intent.data = uri
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}
companion object {
private val TAG = "MAIN_ACTIVITY"
}
}
I might try to improve that Kotlin code when I have some free time, but that’s what it looks like for the time being.
In summary, if you wanted to see some Android location code that uses the FusedLocationProviderClient
API, I hope this code is helpful.
Reporting from Broomfield, Colorado,
Alvin Alexander
Recent blog posts
- Free Scala and functional programming video training courses
- Free: Introduction To Functional Programming video training course
- The #1 functional programming (and computer programming) book
- The User Story Mapping Workshop process
- Alvin Alexander, Certified Scrum Product Owner (CSPO)
- Alvin Alexander is now a Certified ScrumMaster (CSM)
- Our “Back To Then” app (for iOS and Android)
- A Docker cheat sheet
- Pushing a Scala 3 JAR/Docker file to Google Cloud Run
- Reading a CSV File Into a Spark RDD (Scala Cookbook recipe)