0

I am trying to design a good architecture for implementing Google API Services.

The current documentation looks like this:

public class MainActivity extends ActionBarActivity {
public static final String TAG = "BasicHistoryApi";
private static final int REQUEST_OAUTH = 1;
private static final String DATE_FORMAT = "yyyy.MM.dd HH:mm:ss";

/**
 *  Track whether an authorization activity is stacking over the current activity, i.e. when
 *  a known auth error is being resolved, such as showing the account chooser or presenting a
 *  consent dialog. This avoids common duplications as might happen on screen rotations, etc.
 */
private static final String AUTH_PENDING = "auth_state_pending";
private boolean authInProgress = false;

private GoogleApiClient mClient = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // This method sets up our custom logger, which will print all log messages to the device
    // screen, as well as to adb logcat.
    initializeLogging();

    if (savedInstanceState != null) {
        authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
    }

    buildFitnessClient();
}

/**
 *  Build a {@link GoogleApiClient} that will authenticate the user and allow the application
 *  to connect to Fitness APIs. The scopes included should match the scopes your app needs
 *  (see documentation for details). Authentication will occasionally fail intentionally,
 *  and in those cases, there will be a known resolution, which the OnConnectionFailedListener()
 *  can address. Examples of this include the user never having signed in before, or
 *  having multiple accounts on the device and needing to specify which account to use, etc.
 */
private void buildFitnessClient() {
    // Create the Google API Client
    mClient = new GoogleApiClient.Builder(this)
            .addApi(Fitness.HISTORY_API)
            .addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
            .addConnectionCallbacks(
                    new GoogleApiClient.ConnectionCallbacks() {
                        @Override
                        public void onConnected(Bundle bundle) {
                            Log.i(TAG, "Connected!!!");
                            // Now you can make calls to the Fitness APIs.  What to do?
                            // Look at some data!!
                            new InsertAndVerifyDataTask().execute();
                        }

                        @Override
                        public void onConnectionSuspended(int i) {
                            // If your connection to the sensor gets lost at some point,
                            // you'll be able to determine the reason and react to it here.
                            if (i == ConnectionCallbacks.CAUSE_NETWORK_LOST) {
                                Log.i(TAG, "Connection lost.  Cause: Network Lost.");
                            } else if (i == ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
                                Log.i(TAG, "Connection lost.  Reason: Service Disconnected");
                            }
                        }
                    }
            )
            .addOnConnectionFailedListener(
                    new GoogleApiClient.OnConnectionFailedListener() {
                        // Called whenever the API client fails to connect.
                        @Override
                        public void onConnectionFailed(ConnectionResult result) {
                            Log.i(TAG, "Connection failed. Cause: " + result.toString());
                            if (!result.hasResolution()) {
                                // Show the localized error dialog
                                GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
                                        MainActivity.this, 0).show();
                                return;
                            }
                            // The failure has a resolution. Resolve it.
                            // Called typically when the app is not yet authorized, and an
                            // authorization dialog is displayed to the user.
                            if (!authInProgress) {
                                try {
                                    Log.i(TAG, "Attempting to resolve failed connection");
                                    authInProgress = true;
                                    result.startResolutionForResult(MainActivity.this,
                                            REQUEST_OAUTH);
                                } catch (IntentSender.SendIntentException e) {
                                    Log.e(TAG,
                                            "Exception while starting resolution activity", e);
                                }
                            }
                        }
                    }
            )
            .build();
}

@Override
protected void onStart() {
    super.onStart();
    // Connect to the Fitness API
    Log.i(TAG, "Connecting...");
    mClient.connect();
}

@Override
protected void onStop() {
    super.onStop();
    if (mClient.isConnected()) {
        mClient.disconnect();
    }
}

.... // MORE CODE
}

This looks really ugly inside an Activity, what if I have multiple Activities using Google API Services.

Would it be possible to move everything to a Client.java class that just handles creation of a GoogleApiClient object.

How would I pass an activity context parameter to GoogleApiClient.Builder(this)? Should I use an event bus driven system that sends off context values from each activity to the client and builds it each time?

This is pretty ugly, any way I can trim this code so I don't have to copy it everywhere in like 30 activities?

How about a manager class GoogleApiManager.java that would handle all that for me? What sorts of interfaces would I need to implement on this?

Can I instead store inside an application class instead?

Would appreciate any help on this.

AndyRoid
  • 5,062
  • 8
  • 38
  • 73

1 Answers1

1

You are going to have to mess around with the code to get it all working correctly. I don't have google api client hooked up so I can't debug.

You could create a separate class like below

public class BuildFitnessClient {
    private static boolean mAuthInProgress;
    private static final String TAG = "BasicHistoryApi";
    private static final int REQUEST_OAUTH = 1;

    public static GoogleApiClient googleApiClient(final Activity activity, boolean authInProgress) {
        mAuthInProgress = authInProgress;
        return new GoogleApiClient.Builder(activity)
            .addApi(Fitness.HISTORY_API)
            .addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
            .addConnectionCallbacks(
                new GoogleApiClient.ConnectionCallbacks() {
                    @Override
                    public void onConnected(Bundle bundle) {
                        mCallbacks.connected();
                    }

                    @Override
                    public void onConnectionSuspended(int i) {

                        if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
                            Log.i(TAG, "Connection lost.  Cause: Network Lost.");
                        }
                    }
                }
            )
            .addOnConnectionFailedListener(
                new GoogleApiClient.OnConnectionFailedListener() {
                    // Called whenever the API client fails to connect.
                    @Override
                    public void onConnectionFailed(ConnectionResult result) {
                        Log.i(TAG, "Connection failed. Cause: " + result.toString());
                        if (!result.hasResolution()) {
                            // Show the localized error dialog
                            GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
                                activity, 0).show();
                            return;
                        }
                        if (!mAuthInProgress) {
                            try {
                                Log.i(TAG, "Attempting to resolve failed connection");
                                mAuthInProgress = true;
                                result.startResolutionForResult(activity,
                                    REQUEST_OAUTH);
                            } catch (IntentSender.SendIntentException e) {
                                Log.e(TAG,
                                    "Exception while starting resolution activity", e);
                            }
                        }
                    }
                }
            )
            .build();
    }

    /**
     * Interface to communicate to the parent activity (MainActivity.java)
     */
    private static MyCallbacks mCallbacks;


    public interface MyCallbacks {

        void connected();
    }

    public void onAttach(Activity activity) {
        try {
            mCallbacks = (MyCallbacks) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException("Activity must implement Fragment One.");
        }
    }
}

Then in your Activity you could call it like:

public class TestingActivity extends AppCompatActivity implements BuildFitnessClient.MyCallbacks {
    GoogleApiClient mClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_testing);
        new BuildFitnessClient().onAttach(this);
        mClient = new BuildFitnessClient().googleApiClient(this, true);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mClient.isConnected()) {
            mClient.disconnect();
        }
    }

    @Override
    public void connected() {
        Log.e("Connected", "Connected");
        new InsertAndVerifyDataTask().execute();
    }
}
Eugene H
  • 3,520
  • 3
  • 22
  • 39
  • This is a good solution will try to implement this now and see if it works. – AndyRoid May 31 '15 at 02:39
  • @AndyRoid It probably wont work at first considering I didn't really get to debug it. So you are most definetly going to have to play with it to get it working but it should work with some tweaks. – Eugene H May 31 '15 at 02:41
  • @AndyRoid Let me know if you get it working. If not let me know what the issue is. – Eugene H May 31 '15 at 02:58
  • Will do - testing now. – AndyRoid May 31 '15 at 03:42
  • Also would it make sense to make the Client a singleton? Just a thought. – AndyRoid May 31 '15 at 03:43
  • one problem: you are trying to access the boolean inside an anonymous inner class, must be declared final but that still won't work. Solution is to make a static `private static boolean mAuthInProgress` variable but that's ugly as well. Is it possible to move the client out of Activities all together and just use an event driven system to hit the client? Also you don't have a constructor in your code either so the line `buildFitnessClient = new BuildFitnessClient();` and `buildFitnessClient.googleApiClient(...);` doesn't make sense – AndyRoid May 31 '15 at 04:04
  • Just updated the answer. I wish it was not so late here I would put more time into it. I can give it a hack in the morning if you can not get it working. – Eugene H May 31 '15 at 04:08
  • Edited the code it should read `BuildFitnessClient.googleApiClient(this, true);` – AndyRoid May 31 '15 at 04:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/79235/discussion-between-eugene-h-and-andyroid). – Eugene H May 31 '15 at 04:13