> ## Documentation Index
> Fetch the complete documentation index at: https://docs-dev-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Android用モバイルアプリの実装方法（モバイルアプリとAPI）

> モバイル およびAPIアーキテクチャシナリオにおけるAndroidの実装

この文書はモバイルおよびAPIアーキテクチャシナリオの一部であり、Androidでモバイルアプリケーションを実装する方法を説明します。実装したソリューションについての情報は、シナリオを参照してください。

## 1.アプリケーションのセットアップ方法

### サンプルプロジェクト

始めるには、このチュートリアル固有のサンプルプロジェクトをダウンロードします。

[ダウンロード](/docs/package/v2?org=auth0-samples\&repo=auth0-pnp-exampleco-timesheets\&path=timesheets-mobile/android)
[Fork（Github）](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-mobile/android)

システム要件

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

要件を表示

### 依存関係の設定

この実装ではアプリの`build.gradle`ファイル内で以下の依存関係を使用します。

* [Auth0.Android](https://github.com/auth0/Auth0.Android)：このパッケージは、ユーザーを認証するためにAuth0との統合を可能にします。
* [OkHttp](http://square.github.io/okhttp/)：このパッケージはNode.JS APIに要求を行うためのHTTPアプリケーションを提供します。
* [JWTDecode.Android](https://github.com/auth0/JWTDecode.Android)：このパッケージは<Tooltip data-tooltip-id="react-containers-DefinitionTooltip-3" href="/docs/ja-jp/glossary?term=json-web-token" tip="JSON Web Token（JWT）: 二者間のクレームを安全に表現するために使用される標準IDトークン形式（および多くの場合、アクセストークン形式）。" cta="用語集の表示">JWT</Tooltip>のデコードを支援します。
* AppCompat：このパッケージは、アクティビティ内でのナビゲーションにおいてツールバーウィジェットの使用を可能にします。

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

### マニフェストの更新

アプリケーションの`AndroidManifest.xml`を開き、インターネット権限を追加します。

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

アプリケーションの詳細も更新して、ツールバーウィジェットを活用するようにします。

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

### 規定値の設定

Auth0 <Tooltip data-tooltip-id="react-containers-DefinitionTooltip-4" href="/docs/ja-jp/glossary?term=client-id" tip="クライアントID: Auth0から登録されたリソースに与えられる識別値。" cta="用語集の表示">Client ID</Tooltip>, Auth0ドメイン、およびAPIのURLを `/res/values/strings.xml`にある`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>
```

この実装ではアプリケーションパッケージ内にactivities、models、 utilsのディレクトリを作成します。

* `activities/`：このパッケージには `LoginActivity.java`, `TimeSheetActivity.java`、`FormActivity.java`および`UserActivity.java`が含まれます。
* `models/`このパッケージには`TimeSheet.java`および`User.java`データモデルが含まれます。
* `utils/`：このパッケージには`UserProfileManager.java`、`TimeSheetAdapter.java`および`ImageTask.java`が含まれます。

## 2.ユーザーの認可

### マニフェストの更新

アプリの`AndroidManifest.xml`を開き、`ログインアクティビティ`を追加します。

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

### ログインアクティビティの作成

`ログインアクティビティ`ログイン()メソッドを作成して、 WebAuthProviderを初期化し、認可を開始します。`WebAuthProvider`に必ず正確なスキーム、オーディエンス、およびスコープを提供してください。この実装では以下の識別子を使用します。

* **scheme**：`demo`：
* **<Tooltip data-tooltip-id="react-containers-DefinitionTooltip-6" href="/docs/ja-jp/glossary?term=audience" tip="オーディエンス: 発行されたトークンに対するオーディエンスを表す一意の識別子。トークンでaudという名前が付けられ、その値にはIDトークンの場合はアプリケーション（Client ID）、アクセストークンの場合はAPI（API Identifier）のいずれかのIDが含まれます。" cta="用語集の表示">audience</Tooltip>**：`https://api.exampleco.com/timesheet`（Node.JS API）
* **response\_type**：`code`
* **scope**：`timesheets read:timesheets openid profile email offline_accessを作成します。`これらのスコープにより、Node.js APIに対して`POST`および`GET` 要求を行うことができ、ユーザープロフィールやリフレッシュトークンを取得できます。

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

`ログイン()`メソッドで認証が成功すると、ユーザーを`TimeSheetActivity`にリダイレクトします。

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

### 資格情報の保存

ログイン後に受け取った資格情報を保存するためにAuth0.Androidライブラリの`CredentialsManager`と[SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences.html)を使用します。

`ログイン()`メソッドで`WebAuthProvider`を初期化する前に、`CredentialsManager`を作成できます。`CredentialsManager`に`AuthenticationAPIClient`を渡すことで、期限切れのアクセストークンをリフレッシュできます。

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

認証成功後に資格情報が`CredentialsManager`を通じて保存されるように`ログイン（）`メソッドを更新してください。

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

## 3.ユーザープロファイルの取得

### ユーザーモデルの作成

`UserProfileManager`と`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;
    }
}
```

### ユーザープロファイルの保存

ユーザープロファイル情報の保存を管理するために`UserProfileManager`というマネージャークラスを作成します。`UserProfileManager`はデータを保存するために[SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences.html)を使用します。

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

続いて`ログインアクティビティ`の `ログイン()`メソッドを更新してIDトークンを取得し、JWTDecode.Androidライブラリを使用してトークンからユーザープロファイルを取得します。次に`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));
}
// ...
```

## 4.スコープに基づいた条件付きUI要素の表示

ユーザーが特定の操作を実行する権限を持っているかどうかを判断するには、認証プロセス中にユーザーに付与された `scope`を確認します。`scope`にはユーザーに付与されたすべてのスコープを含む文字列が含まれているため、特定のスコープが付与されたかどうかを判断するには、単にそのスコープ文字列に特定のスコープの部分文字列が含まれているかどうかを確認するだけで済みます。

### スコープの保存

まず`User`クラスを更新して、付与されたスコープを保存し、その後、付与されたスコープに特定のスコープが含まれているかどうかを判断するためのヘルパーメソッドである`hasScope()`を提供できます。

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

また、`UserProfileManager`を更新して追加のフィールドを確実に保存してください。

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

次に`LoginActivity`を更新して、`scope`を渡せるようにして、それを`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));
}
// ...
```

### スコープに基づいて承認メニューを表示する

これで、ユーザーに特定のスコープが付与されているかどうかの情報に基づいて、特定のUI要素を表示することができます。例えば、`approve:timesheets`スコープが付与されたユーザーにのみ表示される承認メニュー項目があります。

以下は、`BaseActivity`クラスのコードで、ユーザーが `approve:timesheets`スコープを持っているかどうかをチェックし、それに基づいて承認アクティビティを表示するメニュー項目の可視性の状態を設定します。

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

## 5.APIの呼び出し

### マニフェストの更新

アプリの`AndroidManifest.xml`を開き、 `TimeSheetActivity`を追加します。

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

### TimeSheetActivityのレイアウトを構成する

次に`TimeSheetsActivity`のレイアウトである`timesheet_activity.xml`を作成します。

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

`ListView`ウィジェットには`item_entry.xml`レイアウトで表される個々のエントリが含まれます。

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

`TimeSheetActivity`のツールバーナビゲーションのために`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>
```

### タイムシートモデルを作成する

タイムシートデータをビューで扱うためのモデルを作成します。

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

import java.util.Date;

/**
 * Created by ej on 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;
    }
}
```

### タイムシートアダプターを作成する

`TimeSheetAdapter`はタイムシートエントリの配列を受け取り、それを`TimeSheetActivity`の`ListView`に適用するユーティリティクラスです。

```java lines 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;
    }
}
```

### タイムシートアクティビティの作成

`TimeSheetActivity`はログインしているユーザーのタイムシートエントリをサーバーから取得して表示します。

* `@string/api_url`は`http://10.0.2.2:8080/timesheets`に設定されており、Androidエミュレーターが `http://localhost:8080`で実行されている Node.JS APIに接続できるようになっています。
* `callAPI()`メソッドは、Node.JS APIからタイムシートを取得します。
* `processResults()`メソッドは`callAPI()`からのJSON応答を受け取り、それを`TimeSheet`オブジェクトに変換します。
* `onCreateOptionsMenu(`)と`onOptionsItemSelected(`)メソッドはツールバーウィジェットのナビゲーション機能を処理します。

```java lines 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;
    }
}
```

## 6.ユーザープロファイルを表示する

ログインしているユーザーのプロファイルを表示するには、`UserActivity`を作成し、それに対応する`user_activity.xml`レイアウトと、ツールバーナビゲーション用の`user_action_menu.xml`を作成します。ビューは、ユーザーの名前、メールアドレス、およびプロファイル写真を表示します。

### マニフェストの更新

アプリの`AndroidManifest.xml`を開き、`UserActivity`を追加します。

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

#### ユーザーアクティビティレイアウトを作成する

次に、`UserActivity`のレイアウトである`user_activity.xml`を作成します。これには,ユーザーのプロファイル写真用の`ImageView`と、ユーザーの名前とメールアドレス用の`TextViews`が含まれます。

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

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

続いて `UserActivity`ツールバー用に`user_actions_menu.xml`を作成します。

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

### URLからプロファイル写真を読み込む

URLからプロファイル写真を取り込むには、`AsyncTask`を拡張したタスクを作成し、バックグラウンドで実行します。

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

### ユーザーアクティビティを作成する

`onCreate()`メソッドで、`UserProfileManager`からユーザー情報を取得し、ビューに値を設定します。前と同様に、`onCreateOptionsMenu()`と`onOptionsItemSelected()`メソッドは、ツールバーウィジェットのナビゲーション機能を処理します。

```java lines 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) {
        // Inflate the menu items for use in the action bar
        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:
                // If we got here, the user's action was not recognized.
                // Invoke the superclass to handle it.
                return super.onOptionsItemSelected(item);

        }
        return true;
    }
}
```

## 7.新しいタイムシートのためのフォーム

次に、新しいタイムシートエントリを作成するための`FormActivity`とレイアウトを作成します。

### マニフェストの更新

アプリの`AndroidManifest.xml`を開き、`FormActivity`を追加します。

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

### フォームアクティビティのレイアウトの作成

`form_activity.xml`レイアウトを作成します。これにはプロジェクト名と作業時間の入力用の `EditText` および作業日用の`DatePicker`が含まれます。

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

また、`FormActivity`のツールバー用に`form_actions_menu.xml`を作成します。

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

### Form Activityの作成

* `@string/api_url`は`http://10.0.2.2:8080/timesheets`に設定されており、Androidエミュレーターが `http://localhost:8080`で実行されている Node.JS APIに接続できるようになっています。
* `onCreate()`メソッドはフォームを初期化し、送信ボタンが押されたときに`postAPI()`メソッド用の入力情報を収集します。
* `postAPI()`メソッドはフォームから取得したユーザー入力情報をJSON形式でNode.js APIに送信します。
* `clearForm()`メソッドは入力フォームをクリアします。
* `onCreateOptionsMenu()`と`onOptionsItemSelected()`メソッドはツールバーウィジェットのナビゲーション機能を処理します。

```java lines 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;
    }
}
```

## アプリのテスト

続行する前に、 [Node.js APIを実装している](https://auth0.com/docs/architecture-scenarios/application/mobile-api/api-implementation-nodejs)ことを確認してください。

1. APIのディレクトリにターミナルで移動し、`node server`コマンドを入力してAPIを起動します。
2. 次に、Android Studioでモバイルアプリを開き、\*\*［Run（実行）］\*\*ボタンを押します。
3. Nexus 5X API 23の仮想デバイスを選択します。
4. エミュレーターがモバイルアプリを読み込んだら、ユーザーにログインし、その後、実行中のNode.js APIからタイムシートエントリを作成および表示できます。

これで作業完了です。完了です。
