> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> La implementación en Android para el escenario de arquitectura Mobile + API

# Implementación de la aplicación móvil para Android (Aplicaciones móviles + API)

Este documento forma parte del escenario de arquitectura Mobile + API y explica cómo implementar la aplicación móvil para Android. Consulte el escenario para obtener más información sobre la solución implementada.

<div id="step-1-set-up-the-application">
  ## Paso 1. Configura la aplicación
</div>

<div id="sample-project">
  ### Proyecto de ejemplo
</div>

Descarga un proyecto de ejemplo específico de este tutorial para empezar.

[Descargar](./timesheets-mobile_android.zip)
[Haz un fork en GitHub](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-mobile/android)

Requisitos del sistema

* Android Studio 2.3
* Android SDK 25
* Emulador - Nexus 5X - Android 6.0

Mostrar requisitos

<div id="set-the-dependencies">
  ### Configura las dependencias
</div>

Para esta implementación, usaremos las siguientes dependencias en el archivo `build.gradle` de la app:

* [Auth0.Android](https://github.com/auth0/Auth0.Android): este paquete permite integrar Auth0 para autenticar usuarios.
* [OkHttp](http://square.github.io/okhttp/): este paquete proporciona un cliente HTTP para realizar solicitudes a la API de Node.JS.
* [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android): este paquete ayudará a decodificar <Tooltip tip="JSON Web Token (JWT): formato estándar de ID Token (y, con frecuencia, de token de acceso) que se utiliza para representar claims de forma segura entre dos partes." cta="Ver glosario" href="/es/docs/glossary?term=JWTs">JWTs</Tooltip>.
* AppCompat: este paquete nos permite usar el widget de la barra de herramientas para la navegación en nuestras actividades.

```groovy lines theme={null}
dependencies {
    compile 'com.squareup.okhttp:okhttp:2.7.5'
    compile 'com.auth0.android:auth0:1.10.0'
    compile 'com.auth0.android:jwtdecode:1.1.1'
    compile 'com.android.support:appcompat-v7:25.3.1'
    testCompile 'junit:junit:4.12'
}
```

<div id="update-the-manifest">
  ### Actualiza el manifiesto
</div>

Abre el archivo `AndroidManifest.xml` de la aplicación y agrega el permiso de acceso a Internet:

```xml lines theme={null}
<uses-permission android:name="android.permission.INTERNET" />
```

También actualizaremos los detalles de la aplicación para usar el widget Toolbar:

```xml lines theme={null}
<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
    </application>
```

<div id="set-configuration-values">
  ### Establecer los valores de configuración
</div>

Establece tu Auth0 <Tooltip tip="ID de cliente: valor de identificación que Auth0 asigna a su recurso registrado." cta="Ver glosario" href="/es/docs/glossary?term=Client+ID">ID de cliente</Tooltip>, el dominio de Auth0 y la URL de la API en el recurso `strings.xml` ubicado en `/res/values/strings.xml`:

```xml lines theme={null}
<resources>
    <string name="app_name">ExampleCo Timesheets</string>
    <string name="login">Log in</string>
    <string name="auth0_client_id">...</string>
    <string name="auth0_domain">...</string>
    <string name="api_url">http://10.0.2.2:8080/timesheets</string>
</resources>
```

Para esta implementación, cree directorios para actividades, modelos y utilidades dentro del paquete de la aplicación.

* `activities/`: este paquete contendrá `LoginActivity.java`, `TimeSheetActivity.java`, `FormActivity.java` y `UserActivity.java`.
* `models/`: este paquete contendrá los modelos de datos `TimeSheet.java` y `User.java`.
* `utils/`: este paquete contendrá `UserProfileManager.java`, `TimeSheetAdapter.java` e `ImageTask.java`

<div id="step-2-authorize-the-user">
  ## Paso 2. Autorice al usuario
</div>

<div id="update-the-manifest">
  ### Actualiza el manifiesto
</div>

Abre el archivo `AndroidManifest.xml` de la aplicación y añade la `LoginActivity`:

```xml lines theme={null}
<activity
            android:name="com.auth0.samples.activities.LoginActivity"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="@string/auth0_domain"
                    android:pathPrefix="/android/com.auth0.samples/callback"
                    android:scheme="demo" />
            </intent-filter>
</activity>
```

<div id="create-the-login-activity">
  ### Crear la actividad de inicio de sesión
</div>

`LoginActivity` se encargará de la autorización del usuario y será la primera pantalla que verán los usuarios. Crearemos un método `login()` para inicializar un `WebAuthProvider` e iniciar la autenticación. Asegúrate de proporcionar el scheme, la <Tooltip tip="Audiencia: identificador único de la audiencia de un token emitido. Se denomina aud en un token; su valor contiene el ID de una aplicación (ID de cliente) para un ID Token o de una API (Identificador de API) para un token de acceso." cta="Ver glosario" href="/es/docs/glossary?term=audience">audiencia</Tooltip> y el scope correctos al `WebAuthProvider`. Para esta implementación usaremos:

* **scheme**: `demo`
* **audience**: `https://api.exampleco.com/timesheet`s (la API de Node.JS)
* **response\_type**: `code`
* **scope**: `create:timesheets read:timesheets openid profile email offline_access`. Estos alcances nos permitirán realizar solicitudes `POST` y `GET` a la API de Node.JS, así como recuperar el perfil del usuario y un <Tooltip tip="Token de actualización: token que se usa para obtener un nuevo Token de acceso sin obligar a los usuarios a iniciar sesión nuevamente." cta="Ver glosario" href="/es/docs/glossary?term=Refresh+Token">Token de actualización</Tooltip>.

```java lines theme={null}
private void login() {
        Auth0 auth0 = new Auth0(getString(R.string.auth0_client_id), getString(R.string.auth0_domain));
        auth0.setOIDCConformant(true);

        WebAuthProvider.init(auth0)
                .withScheme("demo")
                .withAudience("https://api.exampleco.com/timesheets")
                .withResponseType(ResponseType.CODE)
                .withScope("create:timesheets read:timesheets openid profile email offline_access")
                .start(
                    // ...
                );
}
```

En el método `login()`, tras autenticarse correctamente, redirigiremos al usuario a `TimeSheetActivity`.

```java lines expandable theme={null}
package com.auth0.samples.activities;

import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.jwt.JWT;
import com.auth0.android.provider.AuthCallback;
import com.auth0.android.provider.ResponseType;
import com.auth0.android.provider.WebAuthProvider;
import com.auth0.android.result.Credentials;
import com.auth0.samples.R;
import com.auth0.samples.models.User;
import com.auth0.samples.utils.CredentialsManager;
import com.auth0.samples.utils.UserProfileManager;

public class LoginActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_activity);
        Button loginWithTokenButton = (Button) findViewById(R.id.loginButton);
        loginWithTokenButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login();
            }
        });
    }

    @Override
    protected void onNewIntent(Intent intent) {
        if (WebAuthProvider.resume(intent)) {
            return;
        }
        super.onNewIntent(intent);
    }

    private void login() {
        Auth0 auth0 = new Auth0(getString(R.string.auth0_client_id), getString(R.string.auth0_domain));
        auth0.setOIDCConformant(true);

        WebAuthProvider.init(auth0)
                .withScheme("demo")
                .withAudience("https://api.exampleco.com/timesheets")
                .withResponseType(ResponseType.CODE)
                .withScope("create:timesheets read:timesheets openid profile email")
                .start(LoginActivity.this, new AuthCallback() {
                    @Override
                    public void onFailure(@NonNull final Dialog dialog) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                dialog.show();
                            }
                        });
                    }

                    @Override
                    public void onFailure(final AuthenticationException exception) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(LoginActivity.this, "Error: " + exception.getMessage(), Toast.LENGTH_SHORT).show();
                            }
                        });
                    }

                    @Override
                    public void onSuccess(@NonNull final Credentials credentials) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(LoginActivity.this, "Log In - Success", Toast.LENGTH_SHORT).show();
                            }
                        });
                        startActivity(new Intent(LoginActivity.this, TimeSheetActivity.class));
                    }
                });
    }
}
```

<div id="store-the-credentials">
  ### Almacenar las credenciales
</div>

Para almacenar las credenciales recibidas después de iniciar sesión, usaremos `CredentialsManager` de la biblioteca Auth0.Android y [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences.html) para almacenarlas.

Antes de inicializar `WebAuthProvider` en el método `login()`, podemos crear `CredentialsManager`. Al pasar un `AuthenticationAPIClient` a `CredentialsManager`, este podrá actualizar los <Tooltip tip="Token de acceso: credencial de autorización, en forma de una cadena opaca o JWT, usada para acceder a una API." cta="Ver glosario" href="/es/docs/glossary?term=Access+Tokens">Tokens de acceso</Tooltip> si han expirado.

```java lines theme={null}
private void login() {
        Auth0 auth0 = new Auth0(getString(R.string.auth0_client_id), getString(R.string.auth0_domain));
        auth0.setOIDCConformant(true);

        AuthenticationAPIClient authAPIClient = new AuthenticationAPIClient(auth0);
        SharedPreferencesStorage sharedPrefStorage = new SharedPreferencesStorage(this);
        final CredentialsManager credentialsManager = new CredentialsManager(authAPIClient, sharedPrefStorage);

        WebAuthProvider.init(auth0)
        // ...
    }
```

Ahora actualiza el método `login()` para que las credenciales se almacenen mediante `CredentialsManager` después de una autenticación exitosa.

```java lines theme={null}
// ...
@Override
public void onSuccess(@NonNull final Credentials credentials) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(LoginActivity.this, "Log In - Success", Toast.LENGTH_SHORT).show();
        }
    });

    credentialsManager.saveCredentials(credentials);
    startActivity(new Intent(LoginActivity.this, TimeSheetActivity.class));
// ...
}
```

<div id="step-3-get-the-user-profile">
  ## Paso 3. Obtener el perfil del usuario
</div>

<div id="create-the-user-model">
  ### Crear el modelo de usuario
</div>

Cree un modelo de usuario sencillo que usarán `UserProfileManager` y `UserActivity`.

```java lines theme={null}
package com.auth0.samples.models;

public class User {
    private String email;
    private String name;
    private String pictureURL;

    public User(String email, String name, String pictureURL) {
        this.email = email;
        this.name = name;
        this.pictureURL = pictureURL;
    }

    public String getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }

    public String getPictureURL() {
        return pictureURL;
    }
}
```

<div id="store-the-user-profile">
  ### Almacenar el perfil del usuario
</div>

Para gestionar el almacenamiento de la información del perfil del usuario, crearemos una clase llamada `UserProfileManager`. `UserProfileManager` usará [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences.html) para almacenar los datos.

```java lines expandable theme={null}
package com.auth0.samples.utils;

import android.content.Context;
import android.content.SharedPreferences;

import com.auth0.android.result.UserProfile;
import com.auth0.samples.models.User;

public class UserProfileManager {

    private static final String PREFERENCES_NAME = "auth0_user_profile";
    private static final String EMAIL = "email";
    private static final String NAME = "name";
    private static final String PICTURE_URL = "picture_url";

    public static void saveUserInfo(Context context, User userInfo) {
        SharedPreferences sp = context.getSharedPreferences(
                PREFERENCES_NAME, Context.MODE_PRIVATE);

        sp.edit()
                .putString(EMAIL, userInfo.getEmail())
                .putString(NAME, userInfo.getName())
                .putString(PICTURE_URL, userInfo.getPictureURL())
                .apply();
    }

    public static User getUserInfo(Context context) {
        SharedPreferences sp = context.getSharedPreferences(
                PREFERENCES_NAME, Context.MODE_PRIVATE);

        return new User(
                sp.getString(EMAIL, null),
                sp.getString(NAME, null),
                sp.getString(PICTURE_URL, null)
        );
    }

    public static void deleteUserInfo(Context context) {
        SharedPreferences sp = context.getSharedPreferences(
                PREFERENCES_NAME, Context.MODE_PRIVATE);

        sp.edit()
                .putString(EMAIL, null)
                .putString(NAME, null)
                .putString(PICTURE_URL, null)
                .apply();
    }
}
```

A continuación, actualiza el método `login()` en `LoginActivity` para recuperar el <Tooltip tip="ID Token: Credencial destinada al propio cliente, en lugar de para acceder a un recurso." cta="Ver glosario" href="/es/docs/glossary?term=ID+Token">ID Token</Tooltip> y obtener el perfil del usuario a partir del token con la biblioteca JWTDecode.Android. Luego, almacena el perfil del usuario con `UserProfileManager`.

```java lines theme={null}
// ...
@Override
public void onSuccess(@NonNull final Credentials credentials) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(LoginActivity.this, "Log In - Success", Toast.LENGTH_SHORT).show();
        }
    });

    credentialsManager.saveCredentials(credentials);
    JWT jwt = new JWT(credentials.getIdToken());
    User user = new User(
            jwt.getClaim("email").asString(),
            jwt.getClaim("name").asString(),
            jwt.getClaim("picture").asString()
    );
    UserProfileManager.saveUserInfo(LoginActivity.this, user);

    startActivity(new Intent(LoginActivity.this, TimeSheetActivity.class));
}
// ...
```

<div id="step-4-display-ui-elements-conditionally-based-on-scope">
  ## Paso 4. Mostrar elementos de la interfaz de usuario de forma condicional según el scope
</div>

Para determinar si un usuario tiene permisos para realizar determinadas acciones, podemos fijarnos en el `scope` que se le otorgó durante el proceso de autenticación. El `scope` contendrá una cadena con todos los alcances concedidos al usuario, así que, para determinar si se concedió un scope concreto, simplemente debemos comprobar si la cadena de alcances contiene la subcadena correspondiente a ese scope.

<div id="store-the-scope">
  ### Almacenar el scope
</div>

Primero, podemos actualizar la clase `User` para almacenar los alcances otorgados y, a continuación, proporcionar un método auxiliar, `hasScope()`, que puede usarse para determinar si los alcances otorgados incluyen un scope determinado:

```java lines expandable theme={null}
public class User {
    private String email;
    private String name;
    private String pictureURL;
    private String grantedScope;

    public User(String email, String name, String pictureURL, String grantedScope) {
        this.email = email;
        this.name = name;
        this.pictureURL = pictureURL;
        this.grantedScope = grantedScope;
    }

    public String getEmail() {
        return email;
    }

    public String getGrantedScope() { 
        return grantedScope; 
    }

    public String getName() {
        return name;
    }

    public String getPictureURL() {
        return pictureURL;
    }

    public Boolean hasScope(String scope) {
        return grantedScope.contains(scope);
    }
}
```

Recuerde también actualizar el `UserProfileManager` para almacenar el campo extra:

```java lines expandable theme={null}
public class UserProfileManager {

    private static final String PREFERENCES_NAME = "auth0_user_profile";
    private static final String EMAIL = "email";
    private static final String NAME = "name";
    private static final String PICTURE_URL = "picture_url";
    private static final String SCOPE = "scope";

    public static void saveUserInfo(Context context, User userInfo) {
        SharedPreferences sp = context.getSharedPreferences(
                PREFERENCES_NAME, Context.MODE_PRIVATE);

        sp.edit()
                .putString(EMAIL, userInfo.getEmail())
                .putString(NAME, userInfo.getName())
                .putString(PICTURE_URL, userInfo.getPictureURL())
                .putString(SCOPE, userInfo.getGrantedScope())
                .apply();
    }

    public static User getUserInfo(Context context) {
        SharedPreferences sp = context.getSharedPreferences(
                PREFERENCES_NAME, Context.MODE_PRIVATE);

        return new User(
                sp.getString(EMAIL, null),
                sp.getString(NAME, null),
                sp.getString(PICTURE_URL, null),
                sp.getString(SCOPE, null)
        );
    }

    public static void deleteUserInfo(Context context) {
        SharedPreferences sp = context.getSharedPreferences(
                PREFERENCES_NAME, Context.MODE_PRIVATE);

        sp.edit()
                .putString(EMAIL, null)
                .putString(NAME, null)
                .putString(PICTURE_URL, null)
                .putString(SCOPE, null)
                .apply();
    }
}
```

A continuación, actualiza `LoginActivity` para pasar también el `scope`, de modo que pueda almacenarse en el objeto `User`:

```java lines theme={null}
// ...
@Override
public void onSuccess(@NonNull final Credentials credentials) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(LoginActivity.this, "Log In - Success", Toast.LENGTH_SHORT).show();
        }
    });

    credentialsManager.saveCredentials(credentials);
    JWT jwt = new JWT(credentials.getIdToken());
    String scopes = credentials.getScope();
    User user = new User(
            jwt.getClaim("email").asString(),
            jwt.getClaim("name").asString(),
            jwt.getClaim("picture").asString(),
            credentials.getScope()
    );
    UserProfileManager.saveUserInfo(LoginActivity.this, user);

    startActivity(new Intent(LoginActivity.this, TimeSheetActivity.class));
}
// ...
```

<div id="display-approval-menu-based-on-scope">
  ### Mostrar el menú de aprobación según el scope
</div>

Ahora podemos mostrar determinados elementos de la interfaz de usuario según se le haya concedido o no al usuario un scope concreto. Por ejemplo, tenemos un elemento del menú de aprobación que solo debe ser visible para los usuarios a los que se les haya concedido el scope `approve:timesheets`.

A continuación, puede ver el código de la clase `BaseActivity`, que comprueba si un usuario tiene el scope `approve:timesheets` y, en función de ello, establece la visibilidad del elemento del menú que muestra la actividad de aprobación:

```java lines theme={null}
// ...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    Boolean canApprove = UserProfileManager.getUserInfo(this).hasScope("approve:timesheets");
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.actions, menu);
    MenuItem item = menu.findItem(R.id.action_approve);
    item.setVisible(canApprove);
    return super.onCreateOptionsMenu(menu);
}
// ...
```

<div id="step-5-call-the-api">
  ## Paso 5. Llama a la API
</div>

<div id="update-the-manifest">
  ### Actualiza el manifiesto
</div>

Abre el archivo `AndroidManifest.xml` de la aplicación y agrega la actividad `TimeSheetActivity`:

```xml wrap lines theme={null}
<activity android:name="com.auth0.samples.activities.TimeSheetActivity" />
```

<div id="create-the-timesheets-activity-layouts">
  ### Cree los diseños de la actividad Timesheets
</div>

A continuación, cree `timesheet_activity.xml`, el diseño de `TimeSheetsActivity`:

```xml lines theme={null}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/navToolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <ListView
        android:id="@+id/timesheetList"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
```

El widget `ListView` contendrá entradas individuales, representadas por el diseño `item_entry.xml`:

```xml lines expandable theme={null}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tvUserID"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UserID"
        android:textStyle="bold" />
    <TextView
        android:id="@+id/tvDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Date" />

    <TextView
        android:id="@+id/tvProjectName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Project"
        android:textStyle="italic" />
    <TextView
        android:id="@+id/tvHours"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hours" />
</LinearLayout>
```

Y para la navegación de la Toolbar en `TimeSheetActivity`, crearemos el recurso de menú `timesheet_action_menu.xml` (`/res/menu/`):

```xml lines theme={null}
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_profile"
        android:title="Profile"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_new"
        android:title="New Timesheet"
        app:showAsAction="always" />
</menu>
```

<div id="create-the-timesheet-model">
  ### Crear el modelo de registro de horas
</div>

Cree un modelo para trabajar con los datos del registro de horas en nuestras vistas:

```java lines expandable theme={null}
package com.auth0.samples.models;

import java.util.Date;

/**
 * Creado por ej el 7/9/17.
 */

public class TimeSheet {
    private String userID;
    private String projectName;
    private String date;
    private double hours;
    private int ID;

    public TimeSheet(String gUserID, String gProjectName, String gDate, double gHours, int gID) {
        this.userID = gUserID;
        this.projectName = gProjectName;
        this.date = gDate;
        this.hours = gHours;
        this.ID = gID;
    }

    public String getUserID() {
        return userID;
    }

    public String getProjectName() {
        return projectName;
    }

    public String getDateString() {
        return date;
    }

    public double getHours() {
        return hours;
    }

    public int getID() {
        return ID;
    }
}
```

<div id="create-the-timesheet-adapter">
  ### Crear el adaptador de registro de horas
</div>

`TimeSheetAdapter` es una clase de utilidad que toma un array de entradas de registro de horas y las aplica al `ListView` de `TimeSheetActivity`.

```java lines expandable theme={null}
package com.auth0.samples.utils;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.auth0.samples.R;
import com.auth0.samples.models.TimeSheet;
import java.util.ArrayList;

public class TimeSheetAdapter extends ArrayAdapter<TimeSheet> {

    public TimeSheetAdapter(Context context, ArrayList<TimeSheet> timesheets) {
        super(context, 0, timesheets);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        TimeSheet timesheet = getItem(position);

        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_entry, parent, false);
        }

        TextView tvUserID = (TextView) convertView.findViewById(R.id.tvUserID);
        TextView tvDate = (TextView) convertView.findViewById(R.id.tvDate);
        TextView tvProjectName = (TextView) convertView.findViewById(R.id.tvProjectName);
        TextView tvHours = (TextView) convertView.findViewById(R.id.tvHours);

        tvUserID.setText(timesheet.getUserID());
        tvDate.setText(timesheet.getDateString());
        tvProjectName.setText(timesheet.getProjectName());
        tvHours.setText(Double.toString(timesheet.getHours()));

        return convertView;
    }
}
```

<div id="create-the-timesheet-activity">
  ### Crear la actividad de registro de horas
</div>

`TimeSheetActivity` muestra las entradas del registro de horas del usuario que ha iniciado sesión y que están almacenadas en el servidor.

* `@string/api_url` se establece en `http://10.0.2.2:8080/timesheets` para que el emulador de Android pueda conectarse a la API de Node.JS que se ejecuta en `http://localhost:8080`.
* El método `callAPI()` recupera los registros de horas de la API de Node.JS.
* El método `processResults()` toma la respuesta JSON de `callAPI()` y la convierte en objetos `TimeSheet`.
* Los métodos `onCreateOptionsMenu(`) y `onOptionsItemSelected(`) gestionan la funcionalidad de navegación del widget Toolbar.

```java lines expandable theme={null}
package com.auth0.samples.activities;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ListView;
import android.widget.Toast;

import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationAPIClient;
import com.auth0.android.authentication.storage.CredentialsManager;
import com.auth0.android.authentication.storage.CredentialsManagerException;
import com.auth0.android.authentication.storage.SharedPreferencesStorage;
import com.auth0.android.callback.BaseCallback;
import com.auth0.android.result.Credentials;
import com.auth0.samples.R;
import com.auth0.samples.utils.TimeSheetAdapter;
import com.auth0.samples.models.TimeSheet;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;

public class TimeSheetActivity extends AppCompatActivity {

    private ArrayList<TimeSheet> timesheets = new ArrayList<>();

    private String accessToken;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.timesheet_activity);
        Toolbar navToolbar = (Toolbar) findViewById(R.id.navToolbar);
        setSupportActionBar(navToolbar);

        Auth0 auth0 = new Auth0(getString(R.string.auth0_client_id), getString(R.string.auth0_domain));
        auth0.setOIDCConformant(true);

        AuthenticationAPIClient authAPIClient = new AuthenticationAPIClient(auth0);
        SharedPreferencesStorage sharedPrefStorage = new SharedPreferencesStorage(this);

        CredentialsManager credentialsManager = new CredentialsManager(authAPIClient, sharedPrefStorage);
        credentialsManager.getCredentials(new BaseCallback<Credentials, CredentialsManagerException>() {
            @Override
            public void onSuccess(Credentials payload) {
                accessToken = payload.getAccessToken();
                callAPI();
            }

            @Override
            public void onFailure(CredentialsManagerException error) {
                Toast.makeText(TimeSheetActivity.this, "Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void callAPI() {
        final Request.Builder reqBuilder = new Request.Builder()
                .get()
                .url(getString(R.string.api_url))
                .addHeader("Authorization", "Bearer " + accessToken);

        OkHttpClient client = new OkHttpClient();
        Request request = reqBuilder.build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                Log.e("API", "Error: ", e);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(TimeSheetActivity.this, "An error occurred", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(final Response response) throws IOException {
                timesheets = processResults(response);
                final TimeSheetAdapter adapter = new TimeSheetAdapter(TimeSheetActivity.this, timesheets);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (response.isSuccessful()) {
                            ListView listView = (ListView) findViewById(R.id.timesheetList);
                            listView.setAdapter(adapter);
                            adapter.addAll(timesheets);
                        } else {
                            Toast.makeText(TimeSheetActivity.this, "API call failed.", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
            }
        });
    }

    private ArrayList<TimeSheet> processResults (Response response) {
        ArrayList<TimeSheet> timesheets = new ArrayList<>();
        try {
            String jsonData = response.body().string();
            if (response.isSuccessful()) {
                JSONArray timesheetJSONArray = new JSONArray(jsonData);
                for (int i = 0; i < timesheetJSONArray.length(); i++) {
                    JSONObject timesheetJSON = timesheetJSONArray.getJSONObject(i);
                    String userID = timesheetJSON.getString("user_id");
                    String projectName = timesheetJSON.getString("project");
                    String dateStr = timesheetJSON.getString("date");
                    Double hours = timesheetJSON.getDouble("hours");
                    int id = timesheetJSON.getInt("id");

                    TimeSheet timesheet = new TimeSheet(userID, projectName, dateStr, hours, id);
                    timesheets.add(timesheet);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return timesheets;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.timesheet_action_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_new:
                startActivity(new Intent(TimeSheetActivity.this, FormActivity.class));
                break;
            case R.id.action_profile:
                startActivity(new Intent(TimeSheetActivity.this, UserActivity.class));
                break;
            default:
                return super.onOptionsItemSelected(item);
        }
        return true;
    }
}
```

<div id="step-6-view-the-user-profile">
  ## Paso 6. Ver el perfil del usuario
</div>

Para mostrar el perfil del usuario que ha iniciado sesión, crearemos `UserActivity`, el diseño `user_activity.xml` correspondiente y `user_action_menu.xml` para la navegación de la Toolbar. La vista mostrará el nombre, el correo electrónico y la foto de perfil del usuario.

<div id="update-the-manifest">
  ### Actualiza el manifiesto
</div>

Abre el archivo `AndroidManifest.xml` de la aplicación y agrega `UserActivity`:

```xml wrap lines theme={null}
<activity android:name="com.auth0.samples.activities.UserActivity" />
```

<div id="create-the-user-activity-layouts">
  #### Crea los diseños de actividad del usuario
</div>

A continuación, crea `user_activity.xml`, el diseño de `UserActivity`, con un `ImageView` para la foto de perfil y `TextViews` para el nombre y el correo electrónico del usuario.

```xml lines expandable theme={null}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/navToolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <ImageView
        android:id="@+id/ivPicture"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:srcCompat="@android:color/darker_gray" />

    <TextView
        android:id="@+id/tvName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Name"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/tvEmail"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Email"
        android:textSize="24sp" />
</LinearLayout>
```

Y cree el archivo `user_actions_menu.xml` para la Toolbar de `UserActivity`:

```xml lines theme={null}
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_view"
        android:title="View Timesheets"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_new"
        android:title="New Timesheet"
        app:showAsAction="always" />
</menu>
```

<div id="load-the-profile-picture-from-url">
  ### Cargar la imagen de perfil desde la URL
</div>

Para cargar la imagen de perfil del usuario desde la URL, cree una tarea que extienda `AsyncTask` y se ejecute en segundo plano.

```java lines expandable theme={null}
package com.auth0.samples.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

import java.io.InputStream;
import java.net.URL;

public class ImageTask extends AsyncTask<String, Void, Bitmap> {

    private ImageView bmImage;

    public ImageTask(ImageView bmImage) {
        this.bmImage = bmImage;
    }

    protected Bitmap doInBackground(String... urls) {
        String urldisplay = urls[0];
        Bitmap mIcon11 = null;

        try {
            InputStream in = new URL(urldisplay).openStream();
            mIcon11 = BitmapFactory.decodeStream(in);
        } catch (Exception e) {
            Log.e("Error", e.getMessage());
            e.printStackTrace();
        }
        return mIcon11;
    }

    protected void onPostExecute(Bitmap result) {
        bmImage.setImageBitmap(result);
    }
}
```

<div id="create-the-user-activity">
  ### Crear la actividad del usuario
</div>

En el método `onCreate()` recuperaremos la información del usuario desde `UserProfileManager` y estableceremos los valores en la vista. Como antes, los métodos `onCreateOptionsMenu()` y `onOptionsItemSelected()` se encargan de la funcionalidad de navegación del widget Toolbar.

```java lines expandable theme={null}
package com.auth0.samples.activities;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;

import com.auth0.samples.R;
import com.auth0.samples.utils.ImageTask;
import com.auth0.samples.utils.UserProfileManager;

public class UserActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.user_activity);
        Toolbar navToolbar = (Toolbar) findViewById(R.id.navToolbar);
        setSupportActionBar(navToolbar);

        TextView tvName = (TextView) findViewById(R.id.tvName);
        TextView tvEmail = (TextView) findViewById(R.id.tvEmail);

        tvName.setText(UserProfileManager.getUserInfo(this).getName());
        tvEmail.setText(UserProfileManager.getUserInfo(this).getEmail());

        new ImageTask((ImageView) findViewById(R.id.ivPicture))
                .execute(UserProfileManager.getUserInfo(this).getPictureURL());

                UserProfileManager.getUserInfo(this).getName();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflar los elementos del menú para usar en la barra de acción
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.user_action_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_new:
                startActivity(new Intent(UserActivity.this, FormActivity.class));
                break;
            case R.id.action_view:
                startActivity(new Intent(UserActivity.this, TimeSheetActivity.class));
                break;
            default:
                // Si llegamos aquí, la acción del usuario no fue reconocida.
                // Invocar la superclase para manejarla.
                return super.onOptionsItemSelected(item);

        }
        return true;
    }
}
```

<div id="step-7-form-for-new-timesheets">
  ## Paso 7. Formulario para nuevos registros de horas
</div>

A continuación, crea `FormActivity` y el diseño para gestionar la creación de nuevas entradas de registro de horas.

<div id="update-the-manifest">
  ### Actualiza el manifiesto
</div>

Abre el archivo `AndroidManifest.xml` de la aplicación y agrega `FormActivity`:

```xml wrap lines theme={null}
<activity android:name="com.auth0.samples.activities.FormActivity" />
```

<div id="create-the-form-activity-layouts">
  ### Crear los diseños de la actividad del formulario
</div>

Crea el diseño `form_activity.xml` con `EditText` para introducir el nombre del proyecto y las horas trabajadas, y un `DatePicker` para la fecha trabajada.

```xml lines expandable theme={null}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mainForm"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="1">

    <android.support.v7.widget.Toolbar
        android:id="@+id/navToolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <EditText
        android:id="@+id/editProjectName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Project Name"
        android:inputType="textPersonName" />

    <EditText
        android:id="@+id/editHours"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Hours Worked"
        android:inputType="number|numberDecimal" />

    <DatePicker
        android:id="@+id/datePicker"
        android:layout_width="match_parent"
        android:layout_height="191dp"
        android:layout_weight="0.93" />

    <Button
        android:id="@+id/submitTimeSheetButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Submit" />
</LinearLayout>
```

Y cree el `form_actions_menu.xml` para la Toolbar de `FormActivity`:

```xml lines theme={null}
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_profile"
        android:title="Profile"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_view"
        android:title="View Timesheets"
        app:showAsAction="always" />
</menu>
```

<div id="create-the-form-activity">
  ### Crear la actividad del formulario
</div>

* `@string/api_url` se configura como `http://10.0.2.2:8080/timesheets` para que el emulador de Android pueda conectarse a la API de Node.JS que se ejecuta en `http://localhost:8080`.
* El método `onCreate()` inicializa el formulario y recopila la entrada para el método `postAPI()` cuando se pulsa el botón de envío.
* El método `postAPI()` enviará la entrada del usuario obtenida del formulario a la API de Node.JS en formato JSON.
* El método `clearForm()` borra los campos del formulario.
* Los métodos `onCreateOptionsMenu()` y `onOptionsItemSelected()` gestionan la funcionalidad de navegación del widget Toolbar.

```java lines expandable theme={null}
package com.auth0.samples.activities;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Toast;

import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationAPIClient;
import com.auth0.android.authentication.storage.CredentialsManager;
import com.auth0.android.authentication.storage.CredentialsManagerException;
import com.auth0.android.authentication.storage.SharedPreferencesStorage;
import com.auth0.android.callback.BaseCallback;
import com.auth0.android.result.Credentials;
import com.auth0.samples.R;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;

public class FormActivity extends AppCompatActivity {

    private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");

    private String accessToken;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.form_activity);
        Toolbar navToolbar = (Toolbar) findViewById(R.id.navToolbar);
        setSupportActionBar(navToolbar);

        Auth0 auth0 = new Auth0(getString(R.string.auth0_client_id), getString(R.string.auth0_domain));
        auth0.setOIDCConformant(true);

        AuthenticationAPIClient authAPIClient = new AuthenticationAPIClient(auth0);
        SharedPreferencesStorage sharedPrefStorage = new SharedPreferencesStorage(this);

        CredentialsManager credentialsManager = new CredentialsManager(authAPIClient, sharedPrefStorage);
        credentialsManager.getCredentials(new BaseCallback<Credentials, CredentialsManagerException>() {
            @Override
            public void onSuccess(Credentials payload) {
                accessToken = payload.getAccessToken();
            }

            @Override
            public void onFailure(CredentialsManagerException error) {
                Toast.makeText(FormActivity.this, "Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });

        Button submitTimeSheetButton = (Button) findViewById(R.id.submitTimeSheetButton);
        final EditText editProjectName = (EditText) findViewById(R.id.editProjectName);
        final EditText editHours = (EditText) findViewById(R.id.editHours);
        final DatePicker datePicker = (DatePicker) findViewById(R.id.datePicker);

        submitTimeSheetButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int day = datePicker.getDayOfMonth();
                int month = datePicker.getMonth();
                int year =  datePicker.getYear();

                Calendar calendar = Calendar.getInstance();
                calendar.set(year, month, day);

                postAPI(
                        editProjectName.getText().toString(),
                        calendar.getTime(),
                        editHours.getText().toString()
                );
            }
        });
    }

    private void postAPI(String projectName, Date date, String hours) {

        JSONObject postBody = new JSONObject();
        try {
            postBody.put("project", projectName);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        try {
            postBody.put("date", date);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        try {
            postBody.put("hours", hours);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        String postStr = postBody.toString();

        final Request.Builder reqBuilder = new Request.Builder()
                .post(RequestBody.create(MEDIA_TYPE_JSON, postStr))
                .url(getString(R.string.api_url))
                .addHeader("Authorization", "Bearer " + accessToken);

        OkHttpClient client = new OkHttpClient();
        Request request = reqBuilder.build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                Log.e("API", "Error: ", e);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(FormActivity.this, "An error occurred", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(final Response response) throws IOException {
                final String resBody = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (response.isSuccessful()) {
                            clearForm((ViewGroup) findViewById(R.id.mainForm));
                            Intent intent = new Intent(FormActivity.this, TimeSheetActivity.class);
                            FormActivity.this.startActivity(intent);
                        } else {
                            Toast.makeText(FormActivity.this, "Timesheet creation failed.", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
            }
        });
    }

    private void clearForm(ViewGroup group) {
        for (int i = 0, count = group.getChildCount(); i < count; ++i) {
            View view = group.getChildAt(i);
            if (view instanceof EditText) {
                ((EditText)view).setText("");
            }

            if(view instanceof ViewGroup && (((ViewGroup)view).getChildCount() > 0))
                clearForm((ViewGroup)view);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.form_action_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_view:
                startActivity(new Intent(FormActivity.this, TimeSheetActivity.class));
                break;
            case R.id.action_profile:
                startActivity(new Intent(FormActivity.this, UserActivity.class));
                break;
            default:
                return super.onOptionsItemSelected(item);

        }
        return true;
    }
}
```

<div id="test-the-app">
  ## Pruebe la aplicación
</div>

Antes de continuar, asegúrese de haber [implementado la API de Node.JS](/es/docs/get-started/architecture-scenarios/mobile-api/api-implementation-nodejs).

1. Inicie la API. Para ello, vaya al directorio de la API en la terminal e introduzca el comando `node server`.
2. Abra la aplicación móvil en Android Studio y presione el botón **Run**.
3. Seleccione el dispositivo virtual Nexus 5X API 23.
4. Una vez que el emulador haya cargado la aplicación móvil, puede iniciar sesión y, a continuación, crear y ver registros de horas desde la API de Node.JS en ejecución.

¡Eso es todo! ¡Ha terminado!
