> ## 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.

> Mobile + API アーキテクチャシナリオにおける API の Android 実装

# Android モバイルアプリケーション実装（Mobile Apps + API）

このドキュメントは Mobile + API アーキテクチャシナリオの一部であり、Android でモバイルアプリケーションを実装する方法について説明します。実装するソリューションの詳細については、このシナリオを参照してください。

<div id="step-1-set-up-the-application">
  ## ステップ 1. アプリケーションをセットアップする
</div>

<div id="sample-project">
  ### サンプルプロジェクト
</div>

まずは、このチュートリアル用のサンプルプロジェクトをダウンロードしてください。

[ダウンロード](./timesheets-mobile_android.zip)
[GitHub でフォーク](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-mobile/android)

システム要件

* Android Studio 2.3
* Android SDK 25
* エミュレーター - Nexus 5X - Android 6.0

要件を表示

<div id="set-the-dependencies">
  ### 依存関係を設定する
</div>

この実装では、アプリの`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 tip="JSON Web Token（JWT）: 2者間でクレームを安全に表現するために使用される標準的な IDトークン形式（多くの場合、アクセストークン形式としても使用）。" cta="用語集を表示" href="/ja/docs/glossary?term=JWTs">JWTs</Tooltip>のデコードに役立ちます。
* AppCompat: このパッケージを使用すると、アクティビティ内のナビゲーションに toolbar ウィジェットを利用できます。

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

### マニフェストを更新する

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

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

アプリケーションの詳細も更新し、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">
  ### 設定値を指定する
</div>

`/res/values/strings.xml` にある `strings.xml` リソースに、Auth0 の <Tooltip tip="クライアントID: Auth0 から登録済みリソースに付与される識別値です。" cta="用語集を見る" href="/ja/docs/glossary?term=Client+ID">クライアントID</Tooltip>、Auth0 ドメイン、および API の URL を設定します。

```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` を配置します。

<div id="step-2-authorize-the-user">
  ## ステップ 2. ユーザーを認可する
</div>

### マニフェストを更新する

アプリの`AndroidManifest.xml`を開き、`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">
  ### Login Activity を作成する
</div>

`LoginActivity` はユーザーの認可を処理し、ユーザーに最初に表示される画面になります。`WebAuthProvider` を初期化して認証を開始するための `login()` メソッドを作成します。`WebAuthProvider` には、正しい scheme、<Tooltip tip="対象者: 発行されたトークンの対象者を一意に識別する ID です。トークン内では aud という名前で表され、その値には IDトークンの場合はアプリケーション（クライアントID）の ID、アクセストークンの場合は API（API Identifier）の ID が含まれます。" cta="用語集を見る" href="/ja/docs/glossary?term=audience">audience</Tooltip>、およびスコープを指定してください。この実装では次を使用します。

* **scheme**: `demo`
* **audience**: `https://api.exampleco.com/timesheet`s (Node.JS API)
* **response\_type**: `code`
* **scope**: `create:timesheets read:timesheets openid profile email offline_access`。これらのスコープにより、Node.JS API に対して `POST` と `GET` を実行できるほか、ユーザープロフィールと <Tooltip tip="リフレッシュトークン: ユーザーに再度ログインさせることなく、新しいアクセストークンを取得するために使用するトークンです。" cta="用語集を見る" href="/ja/docs/glossary?term=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(
                    // ...
                );
}
```

`login()` メソッドでは、認証に成功すると、ユーザーは `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">
  ### 認証情報を保存する
</div>

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

`login()` メソッドで `WebAuthProvider` を初期化する前に、`CredentialsManager` を作成できます。`AuthenticationAPIClient` を `CredentialsManager` に渡すと、有効期限が切れた場合に <Tooltip tip="アクセストークン: API へのアクセスに使用される、不透明な文字列または JWT の形式の認可資格情報。" cta="用語集を見る" href="/ja/docs/glossary?term=Access+Tokens">アクセストークン</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);

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

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

認証が成功したら認証情報が`CredentialsManager`を介して保存されるように、`login()`メソッドを更新します。

```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">
  ## ステップ 3. ユーザープロファイルを取得する
</div>

<div id="create-the-user-model">
  ### ユーザーモデルを作成する
</div>

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

<div id="store-the-user-profile">
  ### ユーザープロファイルを保存する
</div>

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

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

次に、`LoginActivity` の `login()` メソッドを更新して、<Tooltip tip="IDトークン: リソースへのアクセス用ではなく、クライアント自体向けの認証情報です。" cta="用語集を表示" href="/ja/docs/glossary?term=ID+Token">IDトークン</Tooltip> を取得し、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));
}
// ...
```

<div id="step-4-display-ui-elements-conditionally-based-on-scope">
  ## ステップ 4. スコープに基づいて UI 要素を条件付きで表示する
</div>

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

<div id="store-the-scope">
  ### スコープを保存する
</div>

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

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

追加のフィールドを保存できるよう、`UserProfileManager` も忘れずに更新してください。

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

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

<div id="display-approval-menu-based-on-scope">
  ### スコープに基づいて承認メニューを表示する
</div>

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

以下は、ユーザーが `approve:timesheets` スコープを持っているかどうかを `BaseActivity` クラスで確認し、その結果に応じて承認アクティビティを表示するメニュー項目の表示／非表示を設定するコードです。

```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">
  ## 手順 5. API を呼び出す
</div>

<div id="update-the-manifest">
  ### マニフェスト を更新する
</div>

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

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

<div id="create-the-timesheets-activity-layouts">
  ### Timesheets Activity のレイアウトを作成する
</div>

次に、`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 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>
```

また、`TimeSheetActivity` の Toolbar のナビゲーション用に、`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">
  ### タイムシートモデルを作成する
</div>

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

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

import java.util.Date;

/**
 * ej により 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">
  ### TimeSheetAdapter を作成する
</div>

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

```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">
  ### Timesheet Activity を作成する
</div>

`TimeSheetActivity` は、サーバーに保存されている、ログイン中のユーザーのタイムシートエントリを表示します。

* Android Emulator から `http://localhost:8080` で実行中の Node.JS API に接続できるよう、`@string/api_url` は `http://10.0.2.2:8080/timesheets` に設定されています。
* `callAPI()` メソッドは、Node.JS API からタイムシートを取得します。
* `processResults()` メソッドは、`callAPI()` から返された JSON レスポンスを `TimeSheet` オブジェクトに変換します。
* `onCreateOptionsMenu(`) メソッドと `onOptionsItemSelected(`) メソッドは、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">
  ## ステップ 6. ユーザープロファイルを表示する
</div>

ログイン中のユーザーのプロフィールを表示するため、`UserActivity`、対応する `user_activity.xml` レイアウト、そして Toolbar ナビゲーション用の `user_action_menu.xml` を作成します。このビューには、ユーザーの名前、メールアドレス、プロフィール画像が表示されます。

### マニフェストを更新する

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

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

<div id="create-the-user-activity-layouts">
  #### ユーザーアクティビティのレイアウトを作成する
</div>

次に、プロフィール画像用の `ImageView` と、ユーザーの名前およびメールアドレス用の `TextViews` を含む、`UserActivity` のレイアウト `user_activity.xml` を作成します。

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

`UserActivity` の Toolbar 用に `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>
```

<div id="load-the-profile-picture-from-url">
  ### URLからプロフィール画像を読み込む
</div>

URLからユーザーのプロフィール画像を読み込むには、`AsyncTask` を継承し、バックグラウンドで実行するタスクを作成します。

```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">
  ### ユーザーアクティビティを作成する
</div>

`onCreate()` メソッドでは、`UserProfileManager` からユーザー情報を取得し、その値をビューに設定します。前と同様に、`onCreateOptionsMenu()` メソッドと `onOptionsItemSelected()` メソッドが、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) {
        // アクションバーで使用するメニュー項目をインフレートする
        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:
                // ここに到達した場合、ユーザーのアクションは認識されなかった。
                // スーパークラスを呼び出して処理する。
                return super.onOptionsItemSelected(item);

        }
        return true;
    }
}
```

<div id="step-7-form-for-new-timesheets">
  ## ステップ 7. 新しいタイムシート用フォーム
</div>

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

### マニフェストを更新する

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

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

<div id="create-the-form-activity-layouts">
  ### フォームアクティビティのレイアウトを作成する
</div>

プロジェクト名と作業時間の入力には `EditText`、作業日の指定には `DatePicker` を使用した `form_activity.xml` レイアウトを作成します。

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

`FormActivity` のToolbar用に、`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>
```

<div id="create-the-form-activity">
  ### フォームアクティビティを作成する
</div>

* `@string/api_url` は `http://10.0.2.2:8080/timesheets` に設定されています。これにより、Android Emulator から `http://localhost:8080` で実行中の Node.JS API に接続できます。
* `onCreate()` メソッドはフォームを初期化し、送信ボタンが押されたときに `postAPI()` メソッドで使用する入力を取得します。
* `postAPI()` メソッドは、フォームから取得したユーザー入力を JSON 形式で Node.JS API に送信します。
* `clearForm()` メソッドはフォームの入力内容をクリアします。
* `onCreateOptionsMenu()` メソッドと `onOptionsItemSelected()` メソッドは、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">
  ## アプリをテストする
</div>

先に進む前に、必ず [Node.JS API を実装](/ja/docs/get-started/architecture-scenarios/mobile-api/api-implementation-nodejs) しておいてください。

1. ターミナルで API のディレクトリに移動し、`node server` コマンドを実行して API を起動します。
2. Android Studio でモバイルアプリを開き、**Run** ボタンを押します。
3. Nexus 5X API 23 の仮想デバイスを選択します。
4. エミュレーターでモバイルアプリが起動したら、ユーザーとしてログインし、実行中の Node.JS API を使用してタイムシートエントリを作成・表示できます。

以上で完了です。
