Android GoogleMaps myLocation Permission

11,873

Solution 1

After checking the permissions with ContextCompat.checkSelfPermission, if it's not granted, you can request for it. From the documentation:

Also, you need to deal with connection failures when using GoogleApiClient (documentation). In your code, the connection was failing and you weren't managing it on onConnectionFailed.

And finally, I have removed the following from your code (this was causing a connect failure and are not neccesary to use LocationServices and get the last location):

.addApi(Drive.API)
.addApi(Plus.API)
.addScope(Drive.SCOPE_FILE)

Here is a working example:

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    private static final int FINE_LOCATION_PERMISSION_REQUEST = 1;
    private static final int CONNECTION_RESOLUTION_REQUEST = 2;
    private GoogleApiClient mGoogleApiClient;
    private GoogleMap mMap;
    private Location mLastLocation;

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

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        buildGoogleAPIClient();
    }

    @Override
    protected void onResume() {
        super.onResume();

        buildGoogleAPIClient();
    }

    private void buildGoogleAPIClient() {
        if (mGoogleApiClient == null) {
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();
        }
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        findLocation();
    }

    protected void onStart() {
        mGoogleApiClient.connect();
        super.onStart();
    }

    protected void onStop() {
        mGoogleApiClient.disconnect();
        super.onStop();
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
    }

    @Override
    public void onConnectionSuspended(int i) {
        Toast.makeText(this, "Connection suspended", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onConnectionFailed(@NonNull final ConnectionResult connectionResult) {
        if (connectionResult.hasResolution()) {
            try {
                connectionResult.startResolutionForResult(this, CONNECTION_RESOLUTION_REQUEST);
            } catch (IntentSender.SendIntentException e) {
                // There was an error with the resolution intent. Try again.
                mGoogleApiClient.connect();
            }
        } else {
            Dialog dialog = GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), this, 1);
            dialog.show();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
                                    Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == CONNECTION_RESOLUTION_REQUEST && resultCode == RESULT_OK) {
            mGoogleApiClient.connect();
        }
    }

    private void findLocation() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    FINE_LOCATION_PERMISSION_REQUEST);
        } else {
            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);

            LatLng myLat = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude());
            // Add a marker in Sydney and move the camera
            LatLng sydney = new LatLng(-34, 151);
            mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
            mMap.moveCamera(CameraUpdateFactory.newLatLng(myLat));
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case FINE_LOCATION_PERMISSION_REQUEST: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    findLocation();
                }
            }
        }
    }
}

Solution 2

Before you get the last known location you need to check four things:

  1. Is GoogleApiClient connected?
  2. Is permission granted?
  3. Is Location Enabled?
  4. Is GoogleMap ready?

I recommend to use a state-machine or a bool for each thing and ask for the location only if all four is true. For example:

protected void getLastKnownLocationIfReady(){
    if(isGoogleApiConnected && isPermissionGranted && isLocationEnabled && isGoogleMapReady){
        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);

        LatLng myLat = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude());
        // Add a marker in Sydney and move the camera
        LatLng sydney = new LatLng(-34, 151);
        mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(myLat));
    }
}

call getLastKnownLocationIfReady() function every time you change one of the boolean variables.

so your onConnected would look like for example:

@Override
public void onConnected(@Nullable Bundle bundle) {
    isGoogleApiConnected = true;
    getLastKnownLocationIfReady();
}

check permission like:

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, FINE_LOCATION_PERMISSION_REQUEST);
    } else {
        isPermissionGranted = true;
        getLastKnownLocationIfReady();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case FINE_LOCATION_PERMISSION_REQUEST: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                isPermissionGranted = true;
                getLastKnownLocationIfReady();
            }
        }
    }
}

you can ask for enable the location like:

public void requestLocationAccess(){
    if(mLocationRequest == null) createLocationRequest();
    final LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(mLocationRequest);
    builder.setAlwaysShow(true); //this is the key ingredient

    com.google.android.gms.common.api.PendingResult<LocationSettingsResult> result =
            LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());
    result.setResultCallback(new ResultCallback<LocationSettingsResult>(){
        @Override
        public void onResult(@NonNull LocationSettingsResult result){
            final Status resultStatus = result.getStatus();
            switch(resultStatus.getStatusCode()){
                case LocationSettingsStatusCodes.SUCCESS:
                    // All location settings are satisfied.
                    isLocationEnabled = true;
                    getLastKnownLocationIfReady();
                    break;
                case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                    // Location settings are not satisfied. But could be fixed by showing the user a dialog. You need to override OnActivityResult if you want to handle the user's interaction with the dialog.
                    resultStatus.startResolutionForResult(this, REQUEST_LOCATION);
                    break;

                default:
                    break;
            }
        }
    });
}

modify your onMapReady like:

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    isGoogleMapReady = true;
    getLastKnownLocationIfReady();
}

Solution 3

I was facing the same error as you. To save battery, i wanted to use the lastKnownLocation instead of polling all the time. Sometimes, lastKnownLocation was null, no matter what i checked before.

If you have a look in the documentation, it is said that:

The getLastLocation() method returns a Location object from which you can retrieve the latitude and longitude coordinates of a geographic location. The location object returned may be null in rare cases when the location is not available.

To fix this, i implemented a combination of using the lastKnownLocation, and polling for a new position as a fallback.

My basic structure here is

  1. Check if permission is granted
  2. Check if location is enabled
  3. Try to get lastKnownLocation as described in the docs
  4. If null, request a location update in this docs example

This approach works like a charm. I have'nt managed to figure out a better solution so far, but i would suggest you to try it out.

Solution 4

HERE WAS THE PROBLEM: Android Emulator

Scroll down to "Working with the Extended Controls, Settings, and Help".
Long story short: IF YOU ARE USING AN EMULATOR, YOU HAVE TO FAKE YOUR LOCATION DATA!!!!! The emulator doesn't support wifi (or as far as I can tell, GPS data either).

Click on your "phone" (your emulator) to make sure it's selected, and press ctrl+shift+L Type in any location you want and click "Send" and viola... you'll have a location. I knew something was up when GoogleMaps on my emulator wasn't even working. I would search for directions and it would say "Waiting for Location" and nothing would happen.

Good freaking riddance.

Share:
11,873

Related videos on Youtube

rikkitikkitumbo
Author by

rikkitikkitumbo

i code things and then i get stuck and then i ask people on stackoverflow to unstick

Updated on September 15, 2022

Comments

  • rikkitikkitumbo
    rikkitikkitumbo over 1 year

    With the more recent version of Android... you are supposed to check if the user has given you permission before you use their location info. I've gone through the android docs and this is the code I've come up with.

    I'm checking to see if the user has given permission, if they haven't yet... then we ask and then there's a callback function on the results and so on.

    I've stepped through the code many times and everything seems to be working fine EXCEPT: when I finally try to programmatically get the user's location with fusedLocationApi.getLastLocation ... it returns NULL!!

    I've re-uploaded the code I have.

    Anyone who can figure this out and it actually works for me, you can have all my rep score...

    package com.example.greg.rightnow;
    
    
    import android.content.pm.PackageManager;
    import android.location.Location;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.app.FragmentActivity;
    import android.os.Bundle;
    import android.support.v4.content.ContextCompat;
    import android.Manifest;
    import com.google.android.gms.common.ConnectionResult;
    import com.google.android.gms.common.api.Api;
    import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
    import com.google.android.gms.common.api.GoogleApiClient;
    import com.google.android.gms.common.api.PendingResult;
    import com.google.android.gms.common.api.Status;
    import com.google.android.gms.drive.Drive;
    import com.google.android.gms.location.LocationServices;
    import com.google.android.gms.maps.CameraUpdateFactory;
    import com.google.android.gms.maps.GoogleMap;
    import com.google.android.gms.maps.OnMapReadyCallback;
    import com.google.android.gms.maps.SupportMapFragment;
    import com.google.android.gms.maps.model.LatLng;
    import com.google.android.gms.maps.model.MarkerOptions;
    import com.google.android.gms.plus.Plus;
    
    import java.io.FileDescriptor;
    import java.io.PrintWriter;
    import java.util.concurrent.TimeUnit;
    
    public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, ConnectionCallbacks {
    
        private GoogleApiClient mGoogleApiClient;
        private GoogleMap mMap;
        private Location mLastLocation;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_maps);
            // Obtain the SupportMapFragment and get notified when the map is ready to be used.
            SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                    .findFragmentById(R.id.map);
            mapFragment.getMapAsync(this);
    
            // Create an instance of GoogleAPIClient.
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addApi(Drive.API)
                    .addApi(Plus.API)
                    .addScope(Drive.SCOPE_FILE)
                    .addConnectionCallbacks(this)
            //        .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();
    
    
        }
    
    
        @Override
        public void onConnected(@Nullable Bundle bundle) {
    
            }
    
    
    
    
    
        protected void onStart() {
            mGoogleApiClient.connect();
            super.onStart();
        }
    
        protected void onStop() {
            mGoogleApiClient.disconnect();
            super.onStop();
        }
    
    
    
    
        /**
         * Manipulates the map once available.
         * This callback is triggered when the map is ready to be used.
         * This is where we can add markers or lines, add listeners or move the camera. In this case,
         * we just add a marker near Sydney, Australia.
         * If Google Play services is not installed on the device, the user will be prompted to install
         * it inside the SupportMapFragment. This method will only be triggered once the user has
         * installed Google Play services and returned to the app.
         * Manifest.permission.ACCESS_FINE_LOCATION
         */
    
        @Override
        public void onRequestPermissionsResult(int requestCode,
                                               String permissions[], int[] grantResults) {
    
    
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == grantResults[0]) {
                mMap.setMyLocationEnabled(true);
            }
    
    
            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
    
            LatLng myLat = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude());
            // Add a marker in Sydney and move the camera
            LatLng sydney = new LatLng(-34, 151);
            mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
            mMap.moveCamera(CameraUpdateFactory.newLatLng(myLat));
    
    
        }
    
    
    
    
    
        @Override
        public void onMapReady(GoogleMap googleMap) {
            mMap = googleMap;
    
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                    == 0) {
    
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
    
            }
    
            else if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) ==-1) {
                mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
    
                LatLng myLat = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude());
                // Add a marker in Sydney and move the camera
                LatLng sydney = new LatLng(-34, 151);
                mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
                mMap.moveCamera(CameraUpdateFactory.newLatLng(myLat));
    
    
            }
    
        }
    
        @Override
        public void onConnectionSuspended(int i) {
    
        }
    }
    

    You'll notice that I had to comment out this line:

     //   .addOnConnectionFailedListener(this)
    

    If I uncomment this... the 'this' gets underlined in red and I get an error saying "... in Builder cannot be applied to com.example.greg.MapsActivity etc"

  • rikkitikkitumbo
    rikkitikkitumbo almost 8 years
    I think this would work... but now PERMISSIONS_RESULT_CODE is in red and "can't be resolved"
  • Mehmet K
    Mehmet K almost 8 years
    It's just an int you use to keep track of what you requested. You match it against the requestCode in onRequestPermissionResult
  • rikkitikkitumbo
    rikkitikkitumbo almost 8 years
    ok.. getting close now. The only problem is that the permission message seems to be async... and the rest of my code is running before I choose permission or not
  • Mehmet K
    Mehmet K almost 8 years
    Yea that's not good design. Read the documentation i linked & check some examples to get some insight on how it should be done. Hint : You should only do the rest IF the user grants the request
  • rikkitikkitumbo
    rikkitikkitumbo almost 8 years
    it's 3am and I'm hating google a lot. I'm doing everything in the onRequestPermission callback method. It doesn't really matter if I use the arguments that it uses once I'm that method... because at somepoint I have to do this: if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == 1) { mMap.setMyLocationEnabled(true); } and the permission hasn't been changed! The ActivityCompat.requestPermissions method didn't seem to change anything.
  • rikkitikkitumbo
    rikkitikkitumbo almost 8 years
    re-uploaded code for this... still getting NULL when calling the fusedLocationApi.getLastLocation() method.
  • rikkitikkitumbo
    rikkitikkitumbo almost 8 years
    I've reuploaded the code I have. I'm pretty sure at this point I'm using the requestPermission callback method correctly... but I'm still getting NULL when calling fusedLocationApi.getLastLocation() !!
  • antonio
    antonio almost 8 years
    According to stackoverflow.com/questions/16830047/… "the fused location provider will only maintain background location if at least one client is connected to it. Once the first client connects, it will immediately try to get a location. If your activity is the first client to connect and you call getLastLocation() right away in onConnected(), that might not be enough time for the first location to come in"
  • GeneCode
    GeneCode about 4 years
    This works. I only need to add Location Services installed.