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:

(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