Cómo acceder a las APIs de Google con GoogleApiClient (obsoleto)

Puedes usar el objeto GoogleApiClient ("Cliente de la API de Google") para acceder a las APIs 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 más reciente de GoogleApi 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 APIs de Google.

En esta guía, se muestra cómo realizar las siguientes acciones:

  • Administra automáticamente la conexión con los Servicios de Google Play.
  • Realizar llamadas síncronas y asíncronas a la API para cualquiera de los Servicios de Google Play
  • Administra manualmente la conexión con los Servicios de Google Play en esos casos excepcionales en los que esto sea necesario. Para obtener más información, consulta Conexiones administradas manualmente.
Figura 1: Una ilustración que muestra cómo el Cliente de la API de Google proporciona una interfaz para conectar y realizar 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 posterior) para tu SDK de Android. Si aún no lo hiciste, sigue las instrucciones que se indican en Cómo configurar el SDK de los Servicios de Google Play.

Inicia una conexión administrada de forma automática

Una vez que tu proyecto esté vinculado a la biblioteca de Servicios de Google Play, crea una instancia de GoogleApiClient con las APIs de GoogleApiClient.Builder en el método onCreate() de tu actividad. La clase GoogleApiClient.Builder proporciona métodos que te permiten especificar las APIs de Google que quieres usar y los permisos de OAuth 2.0 deseados. A continuación, se muestra 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 permisos al mismo GoogleApiClient si agregas llamadas adicionales a addApi() y addScope().

Importante: Si agregas la API de Wearable junto con otras APIs a un elemento GoogleApiClient, es posible que se produzcan errores de conexión del cliente en dispositivos que no tengan instalada la app para Wear OS. Para evitar errores de conexión, llama al método addApiIfAvailable() y pasa la API de Wearable para permitir que tu cliente administre correctamente la API faltante. Si deseas obtener más información, consulta Cómo acceder a la API de Wearable.

Para comenzar una conexión administrada de forma automática, debes especificar una implementación de la interfaz OnConnectionFailedListener a fin de recibir errores de conexión que no se puedan resolver. Cuando tu instancia de GoogleApiClient administrada automáticamente intente conectarse a las APIs de Google, mostrará automáticamente la IU para intentar corregir las fallas de conexión que se puedan resolver (por ejemplo, si se deben actualizar los Servicios de Google Play). 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 ConnectionCallbacks si tu app necesita saber cuándo se establece o se suspende la conexión administrada automáticamente. Por ejemplo, si tu app realiza llamadas para escribir datos en las APIs de Google, estas solo se deben invocar 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

        // ...
    }
}

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

Comunícate con los servicios de Google

Después de la conexión, el cliente puede realizar llamadas de lectura y escritura con las APIs específicas del servicio para las que está autorizada tu app, como lo especifican las APIs 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. Para obtener instrucciones, consulta la guía de introducción adecuada para la API que estás utilizando, como Google Drive o el Acceso con Google.

Cuando realizas una solicitud de lectura o escritura con 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 tiene un objeto PendingResult, puede especificar si la solicitud se maneja como una llamada asíncrona o síncrona.

Nota: Tu app puede poner solicitudes de lectura en cola mientras no esté conectada 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 todavía está conectada. Después de que se establece una conexión, se ejecutan las 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 que se ejecuta 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 correspondiente, según lo especificado por la API que estás usando (por ejemplo, DriveApi.MetadataBufferResult).

Usa llamadas síncronas

Si deseas que tu código se ejecute en un orden estrictamente definido, quizás porque se necesita el resultado de una llamada como argumento para otra, puedes hacer que tu solicitud sea síncrona llamando 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 apropiada, según lo especificado por la API que estás usando, por ejemplo, DriveApi.MetadataBufferResult.

Debido a que llamar a await() bloquea el subproceso hasta que llega el resultado, la 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
        // ...
    }
}

Accede a la API de 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 consta de 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 mediante 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 aplicación complementaria de Wear OS en el dispositivo.

Cómo usar la API independiente de Wearable

Si tu app usa la API de Wearable, pero no otras APIs 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 situaciones en las 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 verificació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);

Ignora errores de conexión a la API

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

Puedes registrar una falla de conexión a la API para que se ignore con addApiIfAvailable(). Si una API agregada con addApiIfAvailable() no puede conectarse debido a un error no recuperable (como API_UNAVAILABLE para Wear), esa API se descarta de tu GoogleApiClient y el cliente se conecta a otras APIs. Sin embargo, si alguna conexión a la API falla con un error recuperable (como un intent de resolución de consentimiento de OAuth), falla la operación de conexión con el cliente. Cuando se usa una conexión administrada automáticamente, GoogleApiClient intentará resolver esos errores siempre que sea posible. Cuando se utiliza una conexión administrada de forma manual, 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 para la falla y la API se agregó con addApiIfAvailable(). Para aprender a implementar el manejo manual de las fallas de conexión, consulta Soluciona las fallas de conexión.

Debido a que es posible que las APIs agregadas con addApiIfAvailable() no siempre estén presentes en la instancia de GoogleApiClient conectada, debes agregar una verificación con hasConnectedApi() para proteger las llamadas a estas APIs. Para descubrir por qué una API en particular no pudo conectarse cuando toda la operación de conexión fue exitosa para el cliente, llama a getConnectionResult() y obtén el código de error del objeto ConnectionResult. Si tu cliente llama a una API cuando no está conectada al cliente, 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 permisos, agrégalos como parámetros en tu llamada de método addApiIfAvailable() en lugar de usar el método addScope(). Es posible que no se soliciten los permisos agregados con este enfoque si la conexión a la API falla antes de obtener el consentimiento de OAuth, mientras que los permisos agregados con addScope() siempre se solicitan.

Conexiones administradas de forma manual

En la mayor parte de esta guía, se muestra cómo usar el método enableAutoManage para iniciar una conexión administrada de forma automática con errores resueltos automáticamente. En casi todos los casos, esta es la forma más fácil y óptima de conectarte a las APIs de Google desde tu app para Android. Sin embargo, hay algunas situaciones en las que te recomendamos usar una conexión administrada de forma manual 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 a la API
  • Cómo 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.

Cómo iniciar una conexión administrada de forma manual

Si deseas 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 con 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()

Para administrar 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 un contexto de actividad, la práctica recomendada es llamar a connect() en el método onStart() de tu actividad y a 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 de forma automática.

Si usas GoogleApiClient para conectarte a APIs que requieren autenticación, como Google Drive o Google Play Juegos, es muy probable que tu primer intento de conexión falle y 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 muestra el valor true, tu app puede solicitar que el usuario tome medidas inmediatas para resolver el error llamando a startResolutionForResult() en el objeto ConnectionResult. El método startResolutionForResult() se comporta de la misma manera que startActivityForResult() en esta situación e inicia una actividad apropiada para el contexto que ayuda al usuario a resolver el error (como una actividad que ayuda al usuario a 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. Se mostrará un Dialog que proporcionan los Servicios de Google Play y que es apropiado para el error. El diálogo puede proporcionar solo un mensaje en el que se explique el error o una acción para iniciar una actividad que pueda resolver el error (por ejemplo, cuando el usuario necesita 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 proporcionado por GoogleApiAvailability.getErrorDialog(), tu actividad recibe la devolución de llamada onActivityResult() con el código de resultado RESULT_OK. Luego, tu app podrá 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. De esta manera, se realiza un seguimiento del estado de la app mientras el usuario resuelve el error para evitar intentos repetitivos de resolver el mismo error. Por ejemplo, mientras se muestra el diálogo del selector de cuenta para ayudar al usuario a resolver el error SIGN_IN_REQUIRED, el usuario puede rotar la pantalla. De esta manera, se recrea tu actividad y se hace que se vuelva a llamar al método onStart(), que luego vuelve a llamar a connect(). Esto generará otra llamada a startResolutionForResult(), que crea otro diálogo del selector de cuentas frente al existente.

Este valor booleano cumple su propósito previsto solo si persiste en todas 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 ocurran en el dispositivo.

Cómo mantener el estado mientras resuelves un error

Para evitar ejecutar el código en onConnectionFailed() mientras hay un intento anterior de resolver un error en curso, debes conservar un valor booleano que haga un seguimiento para saber si la app ya intenta 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 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 tienes todo listo para ejecutar tu app de forma segura y conectarte manualmente a los Servicios de Google Play.