Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LocationSettingsRequest dialog to enable GPS - onActivityResult() skipped

Tags:

android

dialog

Part of my app requires location services, so if location is currently turned off, the app will prompt the user to enable it. Here is how I am doing it: (Also seen in this Stack Overflow answer)

LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
            .addLocationRequest(mLocationRequest);
builder.setAlwaysShow(true);

PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());

result.setResultCallback(new ResultCallback<LocationSettingsResult>() 
{
     @Override
     public void onResult(LocationSettingsResult result) 
     {
         final Status status = result.getStatus();
         final LocationSettingsStates = result.getLocationSettingsStates();
         switch (status.getStatusCode()) 
         {
             case LocationSettingsStatusCodes.SUCCESS:
                 // All location settings are satisfied. The client can initialize location
                 // requests here.
                 ...
                 Log.d("onResult", "SUCCESS");
                 break;
             case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                 // Location settings are not satisfied. But could be fixed by showing the user
                 // a dialog.
                 Log.d("onResult", "RESOLUTION_REQUIRED");
                 try 
                 {
                     // Show the dialog by calling startResolutionForResult(),
                     // and check the result in onActivityResult().
                     status.startResolutionForResult(OuterClass.this, REQUEST_LOCATION);
                 } 
                 catch (SendIntentException e) 
                 {
                     // Ignore the error.
                 }
                 break;
             case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                 // Location settings are not satisfied. However, we have no way to fix the
                 // settings so we won't show the dialog.
                 ...
                 Log.d("onResult", "UNAVAILABLE");
                 break;
         }
     }
 });

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
    // This log is never called
    Log.d("onActivityResult()", Integer.toString(resultCode));

    final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);
    switch (requestCode)
    {
        case REQUEST_LOCATION:
            switch (resultCode)
            {
                case Activity.RESULT_OK:
                {
                    // All required changes were successfully made
                    break;
                }
                case Activity.RESULT_CANCELED:
                {
                    // The user was asked to change settings, but chose not to
                    break;
                }
                default:
                {      
                    break;
                }
            }
            break;
    }
}

This code works well, however, onActivityResult() is always skipped. Whether or not the user presses Yes, No, or back from the Dialog, onActivityResult() doesn't run.

I need Android to call onActivityResult() so if the user chooses not to turn on location services, I can handle it appropriately.

Google's developer page (and the code above) explicitly says that onActivityResult() should be called. Anyone know why it's being skipped?

I also don't know what the purpose of this line is:

final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);

Thanks!

Edit: Basic information on the structure of my app:

  • This code is contained within the onResume() method of a Fragment which implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, and LocationListener to receive location updates. Example seen here.
  • In onLocationChanged() the Fragment will have a custom View call invalidate() and re-draw itself with updated information.
like image 743
pez Avatar asked Jul 05 '15 22:07

pez


4 Answers

UPDATE

The original answer below is using Java and the now deprecated SettingsApi.

Here is a more modern approach using Kotlin and SettingsClient:

fun showEnableLocationSetting() {
    activity?.let {
        val locationRequest = LocationRequest.create()
        locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY

        val builder = LocationSettingsRequest.Builder()
                .addLocationRequest(locationRequest)

        val task = LocationServices.getSettingsClient(it)
                .checkLocationSettings(builder.build())

        task.addOnSuccessListener { response ->
            val states = response.locationSettingsStates
            if (states.isLocationPresent) {
                //Do something
            }
        }
        task.addOnFailureListener { e ->
            if (e is ResolvableApiException) {
                try {
                    // Handle result in onActivityResult()
                    e.startResolutionForResult(it,
                            MainActivity.LOCATION_SETTING_REQUEST)
                } catch (sendEx: IntentSender.SendIntentException) { }
            }
        }
    }
}

In MainActivity, define the constant:

companion object {
    const val LOCATION_SETTING_REQUEST = 999
}

ORIGINAL ANSWER:

It looks like the main issue is that you have all of the code in a Fragment, and since startResolutionForResult() needs an Activity passed into it, the Activity is what gets the onActivityResult() callback.

One way to get around that is to use the technique described here, manually call the Fragment's onActivityResult() method from the Activity when the result comes in.

I just got this simple example working.

First, the Activity, which adds the Fragment, and also has functionality to pass along the result of onActivityResult() to the Fragment:

public class MainActivity extends AppCompatActivity{

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

        lFrag = LocationFragment.newInstance();
        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, lFrag).commit();

    }
    
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == LocationFragment.REQUEST_LOCATION){
            lFrag.onActivityResult(requestCode, resultCode, data);
        }
        else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
}

Here is the Fragment, which contains all of the functionality to show the dialog, and handle the result. In this simple example I just used Toast messages to verify that it is working as expected. Note that the main change that I've made here from the code in your question is the use of getActivity() to get the Activity reference needed for the call to startResolutionForResult().

public class LocationFragment extends Fragment
        implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {


    LocationRequest mLocationRequest;
    GoogleApiClient mGoogleApiClient;
    PendingResult<LocationSettingsResult> result;
    final static int REQUEST_LOCATION = 199;

    public static LocationFragment newInstance() {
        LocationFragment fragment = new LocationFragment();
        return fragment;
    }

    public LocationFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this).build();
        mGoogleApiClient.connect();

        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_location, container, false);
    }


    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onConnected(Bundle bundle) {

        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setInterval(30 * 1000);
        mLocationRequest.setFastestInterval(5 * 1000);

        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
                .addLocationRequest(mLocationRequest);
        builder.setAlwaysShow(true);

        result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());

        result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
            @Override
            public void onResult(LocationSettingsResult result) {
                final Status status = result.getStatus();
                //final LocationSettingsStates state = result.getLocationSettingsStates();
                switch (status.getStatusCode()) {
                    case LocationSettingsStatusCodes.SUCCESS:
                        // All location settings are satisfied. The client can initialize location
                        // requests here.
                        //...
                        break;
                    case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                        // Location settings are not satisfied. But could be fixed by showing the user
                        // a dialog.
                        try {
                            // Show the dialog by calling startResolutionForResult(),
                            // and check the result in onActivityResult().
                            status.startResolutionForResult(
                                    getActivity(),
                                    REQUEST_LOCATION);
                        } catch (IntentSender.SendIntentException e) {
                            // Ignore the error.
                        }
                        break;
                    case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                        // Location settings are not satisfied. However, we have no way to fix the
                        // settings so we won't show the dialog.
                        //...
                        break;
                }
            }
        });

    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        Log.d("onActivityResult()", Integer.toString(resultCode));

        //final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);
        switch (requestCode)
        {
            case REQUEST_LOCATION:
                switch (resultCode)
                {
                    case Activity.RESULT_OK:
                    {
                        // All required changes were successfully made
                        Toast.makeText(getActivity(), "Location enabled by user!", Toast.LENGTH_LONG).show();
                        break;
                    }
                    case Activity.RESULT_CANCELED:
                    {
                        // The user was asked to change settings, but chose not to
                        Toast.makeText(getActivity(), "Location not enabled, user cancelled.", Toast.LENGTH_LONG).show();
                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
                break;
        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }

}

Here are the results visually, first the dialog is shown if Location Mode is disabled:

enter image description here

Then, if the user clicks No, the result is passed from the Activity to the Fragment, which shows a Toast:

enter image description here

Same thing when the user clicks Yes, but with a success result, and Location Mode is enabled:

enter image description here

Note that it might be a better option to just keep all of this functionality in the Activity, and then call into a public method in the Fragment when the result comes in.

Here is fully working code for keeping the functionality in the Activity. Of course in this solution, you would need to add a call into the Fragment to update the state of Location Mode after onActivityResult() is called.

public class MainActivity extends AppCompatActivity
        implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {


    LocationRequest mLocationRequest;
    GoogleApiClient mGoogleApiClient;
    PendingResult<LocationSettingsResult> result;
    final static int REQUEST_LOCATION = 199;

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

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this).build();
        mGoogleApiClient.connect();

    }

    @Override
    public void onConnected(Bundle bundle) {

        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setInterval(30 * 1000);
        mLocationRequest.setFastestInterval(5 * 1000);

        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
                .addLocationRequest(mLocationRequest);
        builder.setAlwaysShow(true);

        result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());

        result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
            @Override
            public void onResult(LocationSettingsResult result) {
                final Status status = result.getStatus();
                //final LocationSettingsStates state = result.getLocationSettingsStates();
                switch (status.getStatusCode()) {
                    case LocationSettingsStatusCodes.SUCCESS:
                        // All location settings are satisfied. The client can initialize location
                        // requests here.
                        //...
                        break;
                    case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                        // Location settings are not satisfied. But could be fixed by showing the user
                        // a dialog.
                        try {
                            // Show the dialog by calling startResolutionForResult(),
                            // and check the result in onActivityResult().
                            status.startResolutionForResult(
                                    MainActivity.this,
                                    REQUEST_LOCATION);
                        } catch (SendIntentException e) {
                            // Ignore the error.
                        }
                        break;
                    case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                        // Location settings are not satisfied. However, we have no way to fix the
                        // settings so we won't show the dialog.
                        //...
                        break;
                }
            }
        });

    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        Log.d("onActivityResult()", Integer.toString(resultCode));

        //final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);
        switch (requestCode)
        {
            case REQUEST_LOCATION:
                switch (resultCode)
                {
                    case Activity.RESULT_OK:
                    {
                        // All required changes were successfully made
                        Toast.makeText(MainActivity.this, "Location enabled by user!", Toast.LENGTH_LONG).show();
                        break;
                    }
                    case Activity.RESULT_CANCELED:
                    {
                        // The user was asked to change settings, but chose not to
                        Toast.makeText(MainActivity.this, "Location not enabled, user cancelled.", Toast.LENGTH_LONG).show();
                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
                break;
        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }
}
like image 157
Daniel Nugent Avatar answered Nov 16 '22 04:11

Daniel Nugent


You need to add this to your result callback:

case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
    try {
        fragment.startIntentSenderForResult(status.getResolution().getIntentSender(), REQUEST_CHECK_SETTINGS, null, 0, 0, 0, null);
    } catch (IntentSender.SendIntentException e) {
        // Ignore the error.
    }
    break;

onActivityResult will be called on your fragment, you don't need to call it manually in your activity. This is essentially how startResolutionForResult works.

like image 41
Saša Tarbuk Avatar answered Nov 16 '22 06:11

Saša Tarbuk


When you need to resolve the Status or the ResolvableApiException, I suggest you to leverage the activity.registerForActivityResult API in place of startResolutionForResult:

ActivityResultLauncher<IntentSenderRequest> launcher = activity.registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    // All required changes were successfully made
                } else {
                    // The user was asked to change settings, but chose not to
                }
            }
        });

IntentSenderRequest intentSenderRequest = new IntentSenderRequest.Builder(exception.getResolution()).build();
launcher.launch(intentSenderRequest);

You are using Java, but in case Kotlin is needed:

val launcher = activity.registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // User accepted
        } else {
            // User didn't accepted
        }
    }

val intentSenderRequest = IntentSenderRequest.Builder(exception.resolution).build()
launcher.launch(intentSenderRequest)
like image 32
gianlucaparadise Avatar answered Nov 16 '22 06:11

gianlucaparadise


If you want results back to your fragment than use

startIntentSenderForResult(status.getResolution().getIntentSender(), REQUEST_CODE_LOCATION_SETTING, null, 0, 0, 0, null);

instead of status.startResolutionForResult(YourActivity, LOCATION_REQUEST);

USING above method will deliver result back to your fragment only.

like image 5
Bhaumik Ghodasara Avatar answered Nov 16 '22 04:11

Bhaumik Ghodasara