Fused Location Provider (Java)

RecipeBox's next feature is a "Nearby Grocery Stores" screen — given the user's current position, show stores close by that likely carry the ingredients for a recipe. Getting that position reliably is the job of the Fused Location Provider, part of Google Play Services. Rather than reading raw GPS, WiFi, or cell-tower signals yourself, the Fused Location Provider combines all of them intelligently and hands back the best estimate it can produce for the current conditions — which is why Google recommends it over the older platform LocationManager APIs for almost every use case.

Adding the Dependency

// app/build.gradle
dependencies {
    implementation 'com.google.android.gms:play-services-location:21.3.0'
}

Requesting the Location Permission First

Location is a runtime-dangerous permission, so it must be requested the way the Runtime Permissions lesson covers, before any call to the Fused Location Provider is made. Android offers two location permissions:

PermissionAccuracyRecipeBox needs it for
ACCESS_COARSE_LOCATION Approximate location (city-block level), derived from WiFi/cell. Enough to find "nearby grocery stores" — RecipeBox doesn't need pinpoint precision for this.
ACCESS_FINE_LOCATION Precise GPS-level location. Not required for RecipeBox's grocery-store feature; only worth requesting if a future feature needs turn-by-turn accuracy.
RecipeBox only declares and requests ACCESS_COARSE_LOCATION in its manifest — asking for ACCESS_FINE_LOCATION when coarse is enough is exactly the kind of over-broad permission request the Runtime Permissions lesson warns against.

Getting a One-Time Current Location

Since RecipeBox just needs a single snapshot of "where is the user right now," a one-shot read is the right tool — not continuous tracking.

// In NearbyStoresActivity.java (permission already granted)
private FusedLocationProviderClient fusedLocationClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_nearby_stores);

    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
    fetchCurrentLocation();
}

private void fetchCurrentLocation() {
    if (ContextCompat.checkSelfPermission(this,
            Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        return;
    }

    fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null)
            .addOnSuccessListener(location -> {
                if (location != null) {
                    double lat = location.getLatitude();
                    double lng = location.getLongitude();
                    loadNearbyGroceryStores(lat, lng);
                }
            });
}
Explanation:
  • getCurrentLocation() actively requests a fresh fix, unlike the older getLastLocation() which only returns a cached value that might be stale or null.
  • The result arrives asynchronously in an OnSuccessListener — never block the UI thread waiting for it.

Requesting Continuous Location Updates

RecipeBox's grocery-store screen doesn't need continuous tracking — a one-shot location is enough. But it's worth knowing the pattern briefly, for a feature like "recalculate nearby stores as I walk." Continuous updates use a LocationRequest plus a LocationCallback:

private LocationCallback locationCallback;
private LocationRequest locationRequest;

private void startLocationUpdates() {
    locationRequest = new LocationRequest.Builder(
            Priority.PRIORITY_BALANCED_POWER_ACCURACY, 10000) // every 10 seconds
            .build();

    locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult result) {
            for (Location location : result.getLocations()) {
                loadNearbyGroceryStores(location.getLatitude(), location.getLongitude());
            }
        }
    };

    fusedLocationClient.requestLocationUpdates(
            locationRequest, locationCallback, Looper.getMainLooper());
}

Removing Updates When No Longer Needed

@Override
protected void onStop() {
    super.onStop();
    if (locationCallback != null) {
        fusedLocationClient.removeLocationUpdates(locationCallback);
    }
}
Why this matters: continuous location updates keep the GPS/radio hardware active. Forgetting to call removeLocationUpdates() when the screen is no longer visible means RecipeBox keeps draining battery in the background for a feature the user isn't even looking at — a classic, easy-to-overlook battery drain bug.

Battery vs Accuracy Tradeoffs

PriorityTradeoff
PRIORITY_HIGH_ACCURACYUses GPS aggressively for the best precision; costs the most battery.
PRIORITY_BALANCED_POWER_ACCURACYCity-block accuracy using WiFi/cell; much lighter on battery — the right default for RecipeBox's grocery-store lookup.

Battery cost by priority

PRIORITY_HIGH_ACCURACY
PRIORITY_BALANCED_POWER_ACCURACY
With a latitude/longitude in hand, the next lesson plots it on an actual map — see Google Maps (Java) for adding markers and a SupportMapFragment to RecipeBox's nearby-stores screen.