Acceso a las API de Google con GoogleApiClient (obsoleto)

Puedes usar el objeto GoogleApiClient ("Cliente de la API de Google") para acceder a las API de Google que se proporcionan en la biblioteca de Servicios de Google Play (como Acceso con Google, Juegos y Drive). El cliente de la API de Google proporciona un punto de entrada común a los Servicios de Google Play y administra la conexión de red entre el dispositivo del usuario y cada servicio de Google.

Sin embargo, la interfaz GoogleApi más reciente y sus implementaciones son más fáciles de usar y son la forma preferida de acceder a las APIs de los Servicios de Play. Consulta Acceso a las API de Google.

En esta guía, se muestra cómo hacer lo siguiente:

  • Administra automáticamente tu conexión a los Servicios de Google Play.
  • Realizar llamadas síncronas y asíncronas de API a cualquiera de los Servicios de Google Play
  • Administra tu conexión con los Servicios de Google Play de forma manual en esos casos poco frecuentes en los que sea necesario. Para obtener más información, consulta Conexiones administradas manualmente.
Figura 1: Ilustración que muestra cómo el cliente de la API de Google proporciona una interfaz para conectar y hacer llamadas a cualquiera de los Servicios de Google Play disponibles, como Google Play Juegos y Google Drive.

Para comenzar, primero debes instalar la biblioteca de Servicios de Google Play (revisión 15 o versiones posteriores) de tu SDK de Android. Si aún no lo hiciste, sigue las instrucciones para configurar el SDK de los Servicios de Google Play.

Iniciar una conexión administrada automáticamente

Después de vincular tu proyecto a la biblioteca de Servicios de Google Play, crea una instancia de GoogleApiClient con las API de GoogleApiClient.Builder en el método onCreate() de tu actividad. La clase GoogleApiClient.Builder proporciona métodos que te permiten especificar las API de Google que deseas usar y los alcances de OAuth 2.0 que desees. Este es un ejemplo de código que crea una instancia de GoogleApiClient que se conecta con el servicio de Google Drive:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    .enableAutoManage(this /* FragmentActivity */,
                      this /* OnConnectionFailedListener */)
    .addApi(Drive.API)
    .addScope(Drive.SCOPE_FILE)
    .build();

Puedes agregar varias APIs y varios alcances al mismo GoogleApiClient agregando llamadas adicionales a addApi() y addScope().

Importante: Si agregas la API de Wearable junto con otras API a una GoogleApiClient, es posible que encuentres errores de conexión del cliente en dispositivos que no tengan la app de Wear OS instalada. Para evitar errores de conexión, llama al método addApiIfAvailable() y pasa la API de Wearable a fin de permitir que tu cliente maneje la API que falta. Si deseas obtener más información, consulta Cómo acceder a la API de Wearable.

A fin de comenzar una conexión administrada automáticamente, debes especificar una implementación para la interfaz de OnConnectionFailedListener a fin de recibir errores de conexión que no se pueden resolver. Cuando la instancia GoogleApiClient administrada automáticamente intenta conectarse a las API de Google, mostrará automáticamente la IU para intentar corregir cualquier error de conexión que se pueda resolver (por ejemplo, si los Servicios de Google Play deben actualizarse). Si se produce un error que no se puede resolver, recibirás una llamada a onConnectionFailed().

También puedes especificar una implementación opcional para la interfaz de ConnectionCallbacks si tu app necesita saber cuándo se establece o suspende la conexión administrada automáticamente. Por ejemplo, si tu app realiza llamadas para escribir datos en las APIs de Google, deben invocarse solo después de que se haya llamado al método onConnected().

A continuación, se incluye una actividad de ejemplo que implementa las interfaces de devolución de llamada y las agrega al cliente de la API de Google:

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import gms.drive.*;
import android.support.v4.app.FragmentActivity;

public class MyActivity extends FragmentActivity
        implements OnConnectionFailedListener {
    private GoogleApiClient mGoogleApiClient;

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

        // Create a GoogleApiClient instance
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* FragmentActivity */,
                                  this /* OnConnectionFailedListener */)
                .addApi(Drive.API)
                .addScope(Drive.SCOPE_FILE)
                .build();

        // ...
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // An unresolvable error has occurred and a connection to Google APIs
        // could not be established. Display an error message, or handle
        // the failure silently

        // ...
    }
}

La instancia de GoogleApiClient se conectará automáticamente después de que la actividad llame a onStart() y se desconectará después de llamar a onStop(). Tu app puede comenzar de inmediato a realizar solicitudes de lectura a las APIs de Google después de compilar GoogleApiClient, sin esperar a que se complete la conexión.

Comunícate con los servicios de Google

Después de conectarte, tu cliente puede realizar llamadas de lectura y escritura con las API específicas del servicio para las que está autorizada la app, según lo especifican las API y los permisos que agregaste a tu instancia de GoogleApiClient.

Nota: Antes de realizar llamadas a servicios específicos de Google, es posible que primero debas registrar tu app en Google Developers Console. Si deseas obtener instrucciones, consulta la guía de introducción correspondiente para la API que usas, como Google Drive o el Acceso con Google.

Cuando realizas una solicitud de lectura o escritura mediante GoogleApiClient, el cliente de la API muestra un objeto PendingResult que representa la solicitud. Esto ocurre de inmediato, antes de que se entregue la solicitud al servicio de Google al que llama tu app.

Por ejemplo, esta es una solicitud para leer un archivo de Google Drive que proporciona un objeto PendingResult:

Query query = new Query.Builder()
        .addFilter(Filters.eq(SearchableField.TITLE, filename));
PendingResult<DriveApi.MetadataBufferResult> result = Drive.DriveApi.query(mGoogleApiClient, query);

Una vez que tu app tenga un objeto PendingResult, podrá especificar si la solicitud se maneja como llamada asíncrona o síncrona.

Sugerencia: Tu app puede poner en cola solicitudes de lectura sin estar conectado a los Servicios de Google Play. Por ejemplo, tu app puede llamar a métodos para leer un archivo de Google Drive, independientemente de si tu instancia de GoogleApiClient aún está conectada. Una vez que se establece una conexión, se ejecutan solicitudes de lectura en cola. Las solicitudes de escritura generan un error si tu app llama a los métodos de escritura de los Servicios de Google Play mientras el cliente de la API de Google no está conectado.

Usa llamadas asíncronas

Para que la solicitud sea asíncrona, llama a setResultCallback() en PendingResult y proporciona una implementación de la interfaz ResultCallback. Por ejemplo, esta es la solicitud ejecutada de forma asíncrona:

private void loadFile(String filename) {
    // Create a query for a specific filename in Drive.
    Query query = new Query.Builder()
            .addFilter(Filters.eq(SearchableField.TITLE, filename))
            .build();
    // Invoke the query asynchronously with a callback method
    Drive.DriveApi.query(mGoogleApiClient, query)
            .setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
        @Override
        public void onResult(DriveApi.MetadataBufferResult result) {
            // Success! Handle the query result.
            // ...
        }
    });
}

Cuando tu app recibe un objeto Result en la devolución de llamada onResult(), se entrega como una instancia de la subclase adecuada, como lo especifica la API que estás usando, como DriveApi.MetadataBufferResult.

Cómo usar llamadas síncronas

Si quieres que tu código se ejecute en un orden definido, tal vez porque se necesita el resultado de una llamada como argumento para otro, puedes hacer que tu solicitud sea síncrona si llamas a await() en PendingResult. Esto bloquea el subproceso y muestra el objeto Result cuando se completa la solicitud. Este objeto se entrega como una instancia de la subclase adecuada, como lo especifica la API que estás usando, por ejemplo, DriveApi.MetadataBufferResult.

Debido a que llamar a await() bloquea el subproceso hasta que llega el resultado, tu app nunca debe realizar solicitudes síncronas a las APIs de Google en el subproceso de IU. Tu app puede crear un subproceso nuevo con un objeto AsyncTask y usarlo para realizar la solicitud síncrona.

En el siguiente ejemplo, se muestra cómo realizar una solicitud de archivo a Google Drive como una llamada síncrona:

private void loadFile(String filename) {
    new GetFileTask().execute(filename);
}

private class GetFileTask extends AsyncTask {
    protected void doInBackground(String filename) {
        Query query = new Query.Builder()
                .addFilter(Filters.eq(SearchableField.TITLE, filename))
                .build();
        // Invoke the query synchronously
        DriveApi.MetadataBufferResult result =
                Drive.DriveApi.query(mGoogleApiClient, query).await();

        // Continue doing other stuff synchronously
        // ...
    }
}

Cómo acceder a la API Wearable

La API de Wearable proporciona un canal de comunicación para las apps que se ejecutan en dispositivos portátiles y wearables. La API consiste en un conjunto de objetos de datos que el sistema puede enviar y sincronizar, y objetos de escucha que notifican a tus apps sobre eventos importantes con una capa de datos. La API de Wearable está disponible en dispositivos que ejecutan Android 4.3 (nivel de API 18) o versiones posteriores cuando hay un dispositivo wearable conectado y la app complementaria de Wear OS está instalada en el dispositivo.

Usa la API independiente de Wearable

Si tu app usa la API de Wearable, pero no otras API de Google, puedes agregar esta API llamando al método addApi(). En el siguiente ejemplo, se muestra cómo agregar la API de Wearable a tu instancia de GoogleApiClient:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    .enableAutoManage(this /* FragmentActivity */,
                      this /* OnConnectionFailedListener */)
    .addApi(Wearable.API)
    .build();

En los casos en que la API de Wearable no está disponible, las solicitudes de conexión que incluyen la API de Wearable fallan con el código de error API_UNAVAILABLE.

En el siguiente ejemplo, se muestra cómo determinar si la API de Wearable está disponible:

// Connection failed listener method for a client that only
// requests access to the Wearable API
@Override
public void onConnectionFailed(ConnectionResult result) {
    if (result.getErrorCode() == ConnectionResult.API_UNAVAILABLE) {
        // The Wearable API is unavailable
    }
    // ...
}

Cómo usar la API de Wearable con otras APIs de Google

Si tu app usa la API de Wearable además de otras APIs de Google, llama al método addApiIfAvailable() y pasa la API de Wearable para verificar si está disponible. Puedes usar esta comprobación para ayudar a tu app a manejar correctamente los casos en los que la API no esté disponible.

En el siguiente ejemplo, se muestra cómo acceder a la API de Wearable junto con la API de Drive:

// Create a GoogleApiClient instance
mGoogleApiClient = new GoogleApiClient.Builder(this)
        .enableAutoManage(this /* FragmentActivity */,
                          this /* OnConnectionFailedListener */)
        .addApi(Drive.API)
        .addApiIfAvailable(Wearable.API)
        .addScope(Drive.SCOPE_FILE)
        .build();

En el ejemplo anterior, GoogleApiClient puede conectarse correctamente con Google Drive sin conectarse a la API de Wearable si no está disponible. Después de conectar tu instancia de GoogleApiClient, asegúrate de que la API de Wearable esté disponible antes de realizar las llamadas a la API:

boolean wearAvailable = mGoogleApiClient.hasConnectedApi(Wearable.API);

Cómo ignorar errores de conexión a la API

Si llamas a addApi() y el GoogleApiClient no puede conectarse a la API correctamente, fallará toda la operación de conexión de ese cliente y se activará la devolución de llamada onConnectionFailed().

Puedes registrar una falla de conexión a la API que se pase por alto mediante addApiIfAvailable(). Si una API agregada con addApiIfAvailable() no se puede conectar debido a un error no recuperable (como API_UNAVAILABLE para Wear), esa API se descarta de tu GoogleApiClient y el cliente procede a conectarse a otras API. Sin embargo, si falla la conexión a la API con un error recuperable (como un intent de resolución de consentimiento de OAuth), la operación de conexión del cliente falla. Cuando se usa una conexión administrada automáticamente, GoogleApiClient intentará resolver estos errores cuando sea posible. Cuando usas una conexión administrada manualmente, se entrega una ConnectionResult que contiene un intent de resolución a la devolución de llamada onConnectionFailed(). Las fallas de conexión de la API se ignoran solo si no hay una resolución y la API se agregó con addApiIfAvailable(). Para obtener información sobre cómo implementar el manejo manual de fallas de conexión, consulta Cómo manejar fallas de conexión.

Debido a que es posible que las APIs agregadas con addApiIfAvailable() no siempre estén presentes en la instancia GoogleApiClient conectada, debes proteger las llamadas a estas APIs agregando una verificación con hasConnectedApi(). A fin de descubrir por qué una API en particular no se pudo conectar cuando toda la operación de conexión se realizó correctamente para el cliente, llama a getConnectionResult() y obtén el código de error del objeto ConnectionResult. Si el cliente llama a una API cuando no está conectado a este, la llamada falla con el código de estado API_NOT_AVAILABLE.

Si la API que agregas a través de addApiIfAvailable() requiere uno o más alcances, agrégalos como parámetros en tu llamada al método addApiIfAvailable() en lugar de usar el método addScope(). Es posible que los alcances agregados con este enfoque no se soliciten si la conexión a la API falla antes de obtener el consentimiento de OAuth, mientras que los alcances agregados con addScope() siempre se solicitan.

Conexiones administradas manualmente

En la mayor parte de esta guía, se muestra cómo usar el método enableAutoManage para iniciar una conexión administrada automática con errores resueltos de forma automática. En la mayoría de los casos, esta es la mejor manera y la más fácil de conectarse a las APIs de Google desde tu app para Android. Sin embargo, hay algunas situaciones en las que querrías usar una conexión administrada manualmente a las APIs de Google en tu app:

  • Para acceder a las APIs de Google fuera de una actividad o retener el control de la conexión de la API
  • Para personalizar el manejo y la resolución de errores de conexión

En esta sección, se proporcionan ejemplos de estos y otros casos de uso avanzados.

Iniciar una conexión administrada manualmente

A fin de iniciar una conexión administrada manualmente a GoogleApiClient, debes especificar una implementación para las interfaces de devolución de llamada, ConnectionCallbacks y OnConnectionFailedListener. Estas interfaces reciben devoluciones de llamada en respuesta al método connect() asíncrono cuando la conexión a los Servicios de Google Play se realiza correctamente, falla o se suspende.

    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Drive.API)
            .addScope(Drive.SCOPE_FILE)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build()

Cuando administres una conexión de forma manual, deberás llamar a los métodos connect() y disconnect() en los puntos correctos del ciclo de vida de tu app. En el contexto de una actividad, la práctica recomendada es llamar a connect() en el método onStart() de tu actividad y disconnect() en el método onStop() de tu actividad. Los métodos connect() y disconnect() se llaman automáticamente cuando se usa una conexión administrada automáticamente.

Si usas GoogleApiClient para conectarte a las APIs que requieren autenticación, como Google Drive o Google Play Juegos, es muy probable que falle el primer intento de conexión y que tu app reciba una llamada a onConnectionFailed() con el error SIGN_IN_REQUIRED porque no se especificó la cuenta de usuario.

Cómo controlar las fallas de conexión

Cuando tu app recibe una llamada a la devolución de llamada onConnectionFailed(), debes llamar a hasResolution() en el objeto ConnectionResult proporcionado. Si se muestra un valor verdadero, la app puede solicitar que el usuario realice una acción inmediata para resolver el error llamando a startResolutionForResult() en el objeto ConnectionResult. En este caso, el método startResolutionForResult() se comporta igual que startActivityForResult() y, luego, inicia una actividad adecuada para el contexto que ayuda al usuario a resolver el error (como una actividad que le permite seleccionar una cuenta).

Si hasResolution() muestra un valor falso, la app debe llamar a GoogleApiAvailability.getErrorDialog() y pasar el código de error a este método. Esto muestra un Dialog proporcionado por los Servicios de Google Play que es apropiado para el error. Es posible que el diálogo simplemente proporcione un mensaje que explique el error o una acción para iniciar una actividad que pueda resolver el error (por ejemplo, cuando el usuario necesite instalar una versión más reciente de los Servicios de Google Play).

Por ejemplo, tu método de devolución de llamada onConnectionFailed() debería verse de la siguiente manera:

public class MyActivity extends Activity
        implements ConnectionCallbacks, OnConnectionFailedListener {

    // Request code to use when launching the resolution activity
    private static final int REQUEST_RESOLVE_ERROR = 1001;
    // Unique tag for the error dialog fragment
    private static final String DIALOG_ERROR = "dialog_error";
    // Bool to track whether the app is already resolving an error
    private boolean mResolvingError = false;

    // ...

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        if (mResolvingError) {
            // Already attempting to resolve an error.
            return;
        } else if (result.hasResolution()) {
            try {
                mResolvingError = true;
                result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
            } catch (SendIntentException e) {
                // There was an error with the resolution intent. Try again.
                mGoogleApiClient.connect();
            }
        } else {
            // Show dialog using GoogleApiAvailability.getErrorDialog()
            showErrorDialog(result.getErrorCode());
            mResolvingError = true;
        }
    }

    // The rest of this code is all about building the error dialog

    /* Creates a dialog for an error message */
    private void showErrorDialog(int errorCode) {
        // Create a fragment for the error dialog
        ErrorDialogFragment dialogFragment = new ErrorDialogFragment();
        // Pass the error that should be displayed
        Bundle args = new Bundle();
        args.putInt(DIALOG_ERROR, errorCode);
        dialogFragment.setArguments(args);
        dialogFragment.show(getSupportFragmentManager(), "errordialog");
    }

    /* Called from ErrorDialogFragment when the dialog is dismissed. */
    public void onDialogDismissed() {
        mResolvingError = false;
    }

    /* A fragment to display an error dialog */
    public static class ErrorDialogFragment extends DialogFragment {
        public ErrorDialogFragment() { }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            // Get the error code and retrieve the appropriate dialog
            int errorCode = this.getArguments().getInt(DIALOG_ERROR);
            return GoogleApiAvailability.getInstance().getErrorDialog(
                    this.getActivity(), errorCode, REQUEST_RESOLVE_ERROR);
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
            ((MyActivity) getActivity()).onDialogDismissed();
        }
    }
}

Después de que el usuario completa el diálogo proporcionado por startResolutionForResult() o descarta el mensaje que proporciona GoogleApiAvailability.getErrorDialog(), tu actividad recibe la devolución de llamada onActivityResult() con el código de resultado RESULT_OK. La app puede volver a llamar a connect(). Por ejemplo:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_RESOLVE_ERROR) {
        mResolvingError = false;
        if (resultCode == RESULT_OK) {
            // Make sure the app is not already connected or attempting to connect
            if (!mGoogleApiClient.isConnecting() &&
                    !mGoogleApiClient.isConnected()) {
                mGoogleApiClient.connect();
            }
        }
    }
}

En el código anterior, es probable que hayas notado el valor booleano, mResolvingError. Esto realiza un seguimiento del estado de la app mientras el usuario resuelve el error a fin de evitar intentos repetitivos de resolver el mismo error. Por ejemplo, mientras se muestra el diálogo del selector de cuentas para ayudar al usuario a resolver el error SIGN_IN_REQUIRED, el usuario puede rotar la pantalla. Esto recrea tu actividad y hace que se vuelva a llamar a tu método onStart(), que luego vuelve a llamar a connect(). Esto genera otra llamada a startResolutionForResult(), que crea otro diálogo del selector de cuentas frente al existente.

Este booleano tiene el propósito previsto solo si persiste en las instancias de actividad. En la siguiente sección, se explica cómo mantener el estado de manejo de errores de tu app a pesar de otras acciones del usuario o eventos que ocurren en el dispositivo.

Mantén el estado mientras resuelves un error

Para evitar la ejecución del código en onConnectionFailed() mientras un intento anterior de resolver un error está en curso, debes conservar un valor booleano que haga un seguimiento de si tu app ya está intentando resolver un error.

Como se muestra en el ejemplo de código anterior, tu app debe establecer un valor booleano en true cada vez que llama a startResolutionForResult() o muestra el diálogo de GoogleApiAvailability.getErrorDialog(). Luego, cuando tu app reciba RESULT_OK en la devolución de llamada de onActivityResult(), establece el valor booleano en false.

Para realizar un seguimiento del valor booleano en los reinicios de la actividad (como cuando el usuario rota la pantalla), guarda el valor booleano en los datos de instancia guardados de la actividad con onSaveInstanceState():

private static final String STATE_RESOLVING_ERROR = "resolving_error";

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(STATE_RESOLVING_ERROR, mResolvingError);
}

Luego, recupera el estado guardado durante onCreate():

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

    // ...
    mResolvingError = savedInstanceState != null
            && savedInstanceState.getBoolean(STATE_RESOLVING_ERROR, false);
}

Ya puedes ejecutar tu app de forma segura y conectarte a los Servicios de Google Play de forma manual.