> ## 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 mise en œuvre Android de l’API pour le scénario d’architecture Mobile + API

# Mise en œuvre de l’application mobile Android (Applications mobiles + API)

Ce document fait partie du scénario d’architecture Mobile + API et explique comment mettre en œuvre l’application mobile pour Android. Veuillez consulter le scénario pour en savoir plus sur la solution mise en œuvre.

<div id="step-1-set-up-the-application">
  ## Étape 1. Configurer l’application
</div>

<div id="sample-project">
  ### Projet d’exemple
</div>

Téléchargez un projet d’exemple pour ce tutoriel afin de commencer.

[Télécharger](./timesheets-mobile_android.zip)
[Créer un fork sur Github](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-mobile/android)

Configuration système requise

* Android Studio 2.3
* Android SDK 25
* Émulateur - Nexus 5X - Android 6.0

Afficher la configuration requise

<div id="set-the-dependencies">
  ### Configurer les dépendances
</div>

Pour cette mise en œuvre, nous utiliserons les dépendances suivantes dans le fichier `build.gradle` de l’application :

* [Auth0.Android](https://github.com/auth0/Auth0.Android) : ce package permet l’intégration avec Auth0 pour authentifier les utilisateurs.
* [OkHttp](http://square.github.io/okhttp/) : ce package fournit un client HTTP pour effectuer des requêtes vers l’API Node.JS.
* [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android) : ce package aidera à décoder les <Tooltip tip="JSON Web Token (JWT) : format standard de ID Token (et souvent de access token) utilisé pour représenter des claims de façon sécurisée entre deux parties." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=JWTs">JWTs</Tooltip>.
* AppCompat : ce package nous permet d’utiliser le widget Toolbar pour la navigation dans nos activités.

```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">
  ### Mettre à jour le Manifest
</div>

Ouvrez le fichier `AndroidManifest.xml` de l’application et ajoutez l’autorisation d’accès à Internet :

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

Nous mettrons aussi à jour les détails de l’application pour utiliser le 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">
  ### Définir les valeurs de configuration
</div>

Définissez votre <Tooltip tip="Client ID : valeur d’identification attribuée par Auth0 à la ressource que vous avez enregistrée." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=Client+ID">Client ID</Tooltip>, votre domaine Auth0 et l’URL de l’API dans la ressource `strings.xml` située dans `/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>
```

Pour cette mise en œuvre, créez des répertoires `activities`, `models` et `utils` dans le package de l’application.

* `activities/` : ce package contiendra `LoginActivity.java`, `TimeSheetActivity.java`, `FormActivity.java` et `UserActivity.java`.
* `models/` : ce package contiendra les modèles de données `TimeSheet.java` et `User.java`.
* `utils/` : ce package contiendra `UserProfileManager.java`, `TimeSheetAdapter.java` et `ImageTask.java`

<div id="step-2-authorize-the-user">
  ## Étape 2. Autoriser l’utilisateur
</div>

<div id="update-the-manifest">
  ### Mettre à jour le Manifest
</div>

Ouvrez le `AndroidManifest.xml` de l’application et ajoutez `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">
  ### Créer l’Activity Login
</div>

Le `LoginActivity` gérera l’autorisation de l’utilisateur et sera le premier écran affiché aux utilisateurs. Nous allons créer une méthode `login()` pour initialiser un `WebAuthProvider` et démarrer l’authentification. Assurez-vous de fournir le bon scheme, l’<Tooltip tip="Audience : identifiant unique de l’audience d’un token émis. Nommée aud dans un token, sa valeur contient l’ID soit d’une application (Client ID) pour un ID Token, soit d’une API (API Identifier) pour un Access Token." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=audience">audience</Tooltip> et le scope au `WebAuthProvider`. Pour cette mise en œuvre, nous utiliserons :

* **scheme**: `demo`
* **audience**: `https://api.exampleco.com/timesheet`s (l’API Node.JS)
* **response\_type**: `code`
* **scope**: `create:timesheets read:timesheets openid profile email offline_access`. Ces scopes nous permettront d’envoyer des requêtes `POST` et `GET` à l’API Node.JS, ainsi que de récupérer le profil utilisateur et un <Tooltip tip="Refresh Token : token utilisé pour obtenir un Access Token renouvelé sans obliger les utilisateurs à se connecter de nouveau." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=Refresh+Token">Refresh Token</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(
                    // ...
                );
}
```

Dans la méthode `login()`, une fois l’authentification réussie, on redirigera l’utilisateur vers `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">
  ### Stocker les identifiants
</div>

Pour stocker les identifiants reçus après la connexion, nous utiliserons le `CredentialsManager` de la bibliothèque Auth0.Android ainsi que [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences.html) pour les stocker.

Avant d’initialiser le `WebAuthProvider` dans la méthode `login()`, nous pouvons créer le `CredentialsManager`. En passant un `AuthenticationAPIClient` au `CredentialsManager`, celui-ci peut actualiser les <Tooltip tip="Jeton d’accès : justificatif d’autorisation, sous la forme d’une chaîne opaque ou d’un JWT, utilisé pour accéder à une API." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=Access+Tokens">jetons d’accès</Tooltip> s’ils ont expiré.

```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)
        // ...
    }
```

Mettez maintenant à jour la méthode `login()` afin que les identifiants soient stockés par le `CredentialsManager` une fois l’authentification réussie.

```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">
  ## Étape 3. Récupérer le profil utilisateur
</div>

<div id="create-the-user-model">
  ### Créez le modèle User
</div>

Créez un modèle User simple qui servira à `UserProfileManager` et à `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">
  ### Stocker le profil utilisateur
</div>

Pour gérer le stockage des renseignements du profil utilisateur, nous allons créer une classe de gestion nommée `UserProfileManager`. Le `UserProfileManager` utilisera [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences.html) pour stocker les données.

```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();
    }
}
```

Ensuite, mettez à jour la méthode `login()` dans `LoginActivity` pour récupérer le <Tooltip tip="ID Token : information d'identification destinée au client lui-même, plutôt qu'à l'accès à une ressource." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=ID+Token">ID Token</Tooltip> et extraire le profil utilisateur du jeton à l’aide de la bibliothèque JWTDecode.Android. Enregistrez ensuite le profil utilisateur avec le `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">
  ## Étape 4. Afficher des éléments d’interface conditionnellement selon le scope
</div>

Pour déterminer si un utilisateur a les permissions nécessaires pour effectuer certaines actions, nous pouvons examiner le `scope` qui lui a été accordé pendant le processus d’authentification. Le `scope` contient une chaîne regroupant tous les scopes accordés à un utilisateur. Pour déterminer si un scope en particulier a été accordé, il suffit donc de vérifier si la chaîne de scopes contient la sous-chaîne correspondant à ce scope.

<div id="store-the-scope">
  ### Stocker les scopes
</div>

D’abord, nous pouvons mettre à jour la classe `User` pour stocker les scopes accordés, puis fournir une méthode utilitaire, `hasScope()` , qui permet de déterminer si les scopes accordés contiennent un scope particulier :

```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);
    }
}
```

N’oubliez pas non plus de mettre à jour le `UserProfileManager` pour y stocker le champ supplémentaire :

```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();
    }
}
```

Ensuite, mettez à jour le `LoginActivity` pour transmettre le `scope` afin qu’il soit stocké dans l’objet `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">
  ### Afficher le menu d’approbation en fonction du scope
</div>

Nous pouvons maintenant afficher certains éléments de l’interface utilisateur selon que l’utilisateur s’est vu accorder ou non un scope précis. Par exemple, nous avons un élément de menu d’approbation qui ne devrait être visible que pour les utilisateurs qui ont obtenu le scope `approve:timesheets`.

Vous pouvez voir ci-dessous le code de la classe `BaseActivity` qui vérifie si un utilisateur possède le scope `approve:timesheets` et qui, en conséquence, définit la visibilité de l’élément de menu affichant l’activité d’approbation :

```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">
  ## Étape 5. Faire une requête à l’API
</div>

<div id="update-the-manifest">
  ### Mettre à jour le Manifest
</div>

Ouvrez le fichier `AndroidManifest.xml` de l’application et ajoutez `TimeSheetActivity` :

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

<div id="create-the-timesheets-activity-layouts">
  ### Créez les mises en page de l’Activity Timesheets
</div>

Ensuite, créez `timesheet_activity.xml`, la mise en page 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>
```

Le widget `ListView` contiendra des entrées individuelles, représentées par la mise en page `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>
```

Et pour la navigation de la Toolbar de `TimeSheetActivity`, nous créerons la ressource de menu `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">
  ### Créer le modèle de feuille de temps
</div>

Créez un modèle pour utiliser les données de feuille de temps dans nos vues :

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

import java.util.Date;

/**
 * Créé par ej le 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">
  ### Créer l’adaptateur Timesheet
</div>

Le `TimeSheetAdapter` est une classe utilitaire qui prend un tableau d’entrées de feuille de temps et les applique au `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">
  ### Créer l’Activity Timesheet
</div>

Le `TimeSheetActivity` affiche les entrées de feuille de temps de l’utilisateur connecté, stockées sur le serveur.

* `@string/api_url` est défini sur `http://10.0.2.2:8080/timesheets` afin que l’émulateur Android puisse se connecter à l’API Node.JS en cours d’exécution sur `http://localhost:8080`.
* La méthode `callAPI()` récupère les feuilles de temps à partir de l’API Node.JS.
* La méthode `processResults()` prend la réponse JSON de `callAPI()` et la convertit en objets `TimeSheet`.
* Les méthodes `onCreateOptionsMenu(`) et `onOptionsItemSelected(`) gèrent la navigation du 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">
  ## Étape 6. Afficher le profil de l’utilisateur
</div>

Pour afficher le profil de l’utilisateur connecté, nous allons créer `UserActivity`, le fichier de mise en page correspondant `user_activity.xml`, ainsi que `user_action_menu.xml` pour la navigation dans la Toolbar. Cette vue affichera le nom de l’utilisateur, son adresse courriel et sa photo de profil.

<div id="update-the-manifest">
  ### Mettre à jour le Manifest
</div>

Ouvrez le fichier `AndroidManifest.xml` de l’application et ajoutez `UserActivity` :

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

<div id="create-the-user-activity-layouts">
  #### Créer les mises en page de User Activity
</div>

Ensuite, créez `user_activity.xml`, la mise en page de `UserActivity`, avec un `ImageView` pour la photo de profil et des `TextViews` pour le nom et l’adresse courriel de l’utilisateur.

```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>
```

Et créez le `user_actions_menu.xml` pour 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="Voir les feuilles de temps"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_new"
        android:title="Nouvelle feuille de temps"
        app:showAsAction="always" />
</menu>
```

<div id="load-the-profile-picture-from-url">
  ### Charger la photo de profil depuis l’URL
</div>

Pour charger la photo de profil de l’utilisateur depuis l’URL, créez une tâche qui hérite de `AsyncTask` et s’exécute en arrière-plan.

```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">
  ### Créer la User Activity
</div>

Dans la méthode `onCreate()`, nous récupérerons les renseignements de l’utilisateur à partir de `UserProfileManager` et définirons les valeurs dans la vue. Comme auparavant, les méthodes `onCreateOptionsMenu()` et `onOptionsItemSelected()` gèrent la fonctionnalité de navigation de la 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) {
        // Charger les éléments de menu pour utilisation dans la barre d'action
        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 nous sommes arrivés ici, l'action de l'utilisateur n'a pas été reconnue.
                // Invoquer la superclasse pour la gérer.
                return super.onOptionsItemSelected(item);

        }
        return true;
    }
}
```

<div id="step-7-form-for-new-timesheets">
  ## Étape 7. Formulaire pour les nouvelles feuilles de temps
</div>

Créez ensuite `FormActivity` et le fichier de mise en page pour gérer la création de nouvelles entrées de feuille de temps.

<div id="update-the-manifest">
  ### Mettre à jour le Manifest
</div>

Ouvrez le fichier `AndroidManifest.xml` de l’application et ajoutez `FormActivity` :

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

<div id="create-the-form-activity-layouts">
  ### Créer les mises en page de l’Activity de formulaire
</div>

Créez la mise en page `form_activity.xml` avec des champs `EditText` pour le nom du projet et le nombre d’heures travaillées, ainsi qu’un `DatePicker` pour le jour travaillé.

```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>
```

Créez ensuite le `form_actions_menu.xml` pour la barre d’outils 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="Profil"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_view"
        android:title="Voir les feuilles de temps"
        app:showAsAction="always" />
</menu>
```

<div id="create-the-form-activity">
  ### Créer la Form Activity
</div>

* La valeur de `@string/api_url` est définie sur `http://10.0.2.2:8080/timesheets` afin que l’émulateur Android puisse se connecter à l’API Node.JS qui s’exécute sur `http://localhost:8080`.
* La méthode `onCreate()` initialise le formulaire et recueille les données saisies pour la méthode `postAPI()` lorsque le bouton de soumission est sélectionné.
* La méthode `postAPI()` enverra à l’API Node.JS, au format JSON, les données saisies par l’utilisateur et récupérées à partir du formulaire.
* La méthode `clearForm()` efface les champs de saisie du formulaire.
* Les méthodes `onCreateOptionsMenu()` et `onOptionsItemSelected()` gèrent la fonctionnalité de navigation du 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">
  ## Testez l’application
</div>

Avant de continuer, assurez-vous d’avoir [mis en œuvre l’API Node.JS](/docs/fr-ca/get-started/architecture-scenarios/mobile-api/api-implementation-nodejs).

1. Démarrez l’API en accédant au répertoire de l’API dans votre terminal et en entrant la commande `node server`.
2. Ouvrez l’application mobile dans Android Studio et appuyez sur le bouton **Run**.
3. Sélectionnez l’appareil virtuel Nexus 5X API 23.
4. Une fois l’application mobile chargée dans l’émulateur, vous pouvez alors vous connecter, puis créer et consulter des entrées de feuille de temps à partir de l’API Node.JS en cours d’exécution.

Et voilà ! C’est terminé !
