0

I'm making an android app that uses device sensors and location. The data from both sources will be used to calcute some things about orientation. So far i created two non-activity classes, one for sensor management and one for location management. The problem is when i want to use something that needs Activity or Context to be done, for example: show the dialog by calling startResolutionForResult(), and check the result in onActivityResult(). startResolutionForResult()needs Activity to show a dialog. What's the best approach to solve this? I've decided to use context as a field member of each non-activity class, but i'm not sure if it's the best option.

Update Here is a part of my code i'm reffering to.

In onCreate() method i call checkLocationSettings() method to check if location is on. Then onResult() callback fires where i can determine what to do depending on location settings. When location is off, it shows dialog called by

status.startResolutionForResult((Activity)getBaseContext(), REQUEST_CHECK_SETTINGS)

The result of users decision is available in onActivityResult() which is a part of MainActivity.class Everything works like a charm, but i'm concernd about (Activity)getBaseContext() in particular.

GoogleServiceLocationProvider.class

public class GoogleServiceLocationProvider extends ContextWrapper implements GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener, LocationListener, ResultCallback<LocationSettingsResult> {

private static final int REQUEST_CHECK_SETTINGS = 3;

private static final int UPDATE_INTERVAL = 10 * 1000;   // 10 seconds
private static final int DISPLACEMENT = 10;             // 10 meters

private GoogleApiClient googleApiClient;
private LocationRequest locationRequest;

private Location lastLocation;
private long lastTime;

LocationFinder.LocationUpdateListener locationUpdateListener;
private boolean isListenerRunning;

private LocationSettingsRequest locationSettingsRequest;

public GoogleServiceLocationProvider(Context base) {
    super(base);

    buildApiGoogleClient();
    createLocationRequest();
    buildLocationSettingsRequest();
}

private void buildLocationSettingsRequest() {
    LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
    builder.addLocationRequest(locationRequest);
    locationSettingsRequest = builder.build();
}

public void checkLocationSettings() {
    if(googleApiClient != null && locationSettingsRequest != null){
        PendingResult<LocationSettingsResult> result =
                LocationServices.SettingsApi.checkLocationSettings(
                        googleApiClient,
                        locationSettingsRequest
                );
        result.setResultCallback(this);
    }
}

@Override
public void onResult(@NonNull LocationSettingsResult locationSettingsResult) {
    final Status status = locationSettingsResult.getStatus();
    switch (status.getStatusCode()) {
        case LocationSettingsStatusCodes.SUCCESS:
            break;
        case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
            try {
                status.startResolutionForResult((Activity)getBaseContext(), REQUEST_CHECK_SETTINGS);
            } catch (IntentSender.SendIntentException e) {
            }
            break;
        case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
            break;
    }
}

MainActivity.class

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

private void useGoogleLocationServiceAPI(){
    googleServiceLocationProvider = new GoogleServiceLocationProvider(this);
    googleServiceLocationProvider.checkLocationSettings();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    switch(requestCode){

        case PLAY_SERVICES_RESOLUTION_REQUEST:
            switch(resultCode){
                case RESULT_OK:
                    isGoogleLocationService = true;
                    useGoogleLocationServiceAPI();
                    break;
                case RESULT_CANCELED:
                    Toast.makeText(this, getString(R.string.google_location_service_missing),
                            Toast.LENGTH_SHORT).show();
                    useAndroidLocationAPI();
                    break;
            }
            break;

        case REQUEST_CHECK_SETTINGS:
            switch(resultCode){
                case RESULT_OK:
                    break;
                case RESULT_CANCELED:
                    Toast.makeText(this, getString(R.string.location_off_message),
                            Toast.LENGTH_SHORT).show();
                    break;
            }
            break;
    }
}
sheddar
  • 270
  • 1
  • 4
  • 16

3 Answers3

2

Passing context's to each and every class is one option. But I'd prefer keeping these classes loosely coupled and use an EventBus to send and retrieve data. As It provides a centralized way to notify software components that are interested in a specific type of event. There exists no direct coupling between the code of the publisher of the event and the code that receives it. A well designed decoupled system will reduce the need to make changes across components in case anything changes.

The library is available here: EventBus

Here's an even simpler tutorial that explains the same:How to use the greenrobot EventBus library

OBX
  • 6,044
  • 7
  • 33
  • 77
  • I do agree that `EventBus` is a greet way to manage data transfer. But don't you think this is an overkill for such a question? And the thing is that his classes require a `context` in order to do what they're meant, so you're not decoupling anything by using `EventBus`. – Anis LOUNIS aka AnixPasBesoin Jan 31 '17 at 18:53
  • No, EventBus is a reasonable solution when you're trying to communicate with Activities, because of their transient nature. – EpicPandaForce Jan 31 '17 at 19:14
1

Update

By this line of code status.startResolutionForResult((Activity)getBaseContext(), /*...*/); you are assuming that the context that has been passed to your constructor GoogleServiceLocationProvider is an Activity which might not be always the case. What if you were using a Service? This line (Activity)getBaseContext() would throw a ClassCastException.

If you need an Activity in order to make sure your GoogleServiceLocationProvider has it all, why don't just require an Activity instance instead of a Context in your constructor?

private Activity activity;
public GoogleServiceLocationProvider(Activity activity) {
    super(activity.getContext());
    this. activity = activity;
    //...
}

You would use that later in your onResult method like so:

@Override
public void onResult(@NonNull LocationSettingsResult locationSettingsResult) {
    //...
    status.startResolutionForResult(this.activity,  REQUEST_CHECK_SETTINGS);
    //...
}

Now to answer your first question.

Android fact

You're always running your code where the context is available somehow.

Whether it's an Activity, a Service, an Application, a BoradcastReceiver ... You'll have a direct/indirect access to your context.

Therefore

The simplest yet more efficient and decoupled approach is to pass this context as a reference to your custom classes. You can keep a reference to that context somewhere in your class (dependency injection) in order to use it later but be aware of leaks.

Some code

class MyClass {
    //...
    public void doSomethingWithTheContext (Context context) {
        //...
        context.doThis(/*...*/);
        //...
    }
}

Usage

From an Activity, an Application, a Service ... (all extends Context)

myInstanceOfMyClass.doSomethingWithTheContext(this);

From a Fragment

myInstanceOfMyClass.doSomethingWithTheContext(getActivity());

Related question

Using Application context everywhere?

Community
  • 1
  • 1
0

You could pass either the Context or the Activity objects to a method, like this:

public void startResolutionForResult(Context context) {
    contex.showYourAlert();
}

To get the onActivityResult() values, you could pass them as parameters too, or just create the startResolutionForResult method inside your Activity.

gi097
  • 7,313
  • 3
  • 27
  • 49