ステップ 1. アプリケーションをセットアップする
サンプルプロジェクト
- Android Studio 2.3
- Android SDK 25
- エミュレーター - Nexus 5X - Android 6.0
依存関係を設定する
build.gradleファイルで次の依存関係を使用します。
- Auth0.Android: このパッケージを使用すると、Auth0 と統合してユーザーを認証できます。
- OkHttp: このパッケージは、Node.js API にリクエストを送信するための HTTP クライアントを提供します。
- JWTDecode.Android: このパッケージは、のデコードに役立ちます。
- AppCompat: このパッケージを使用すると、アクティビティ内のナビゲーションに toolbar ウィジェットを利用できます。
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を開き、インターネット アクセス権限を追加します。
<uses-permission android:name="android.permission.INTERNET" />
<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>
設定値を指定する
/res/values/strings.xml にある strings.xml リソースに、Auth0 の 、Auth0 ドメイン、および API の URL を設定します。
<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を開き、LoginActivityを追加します。
<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>
Login Activity を作成する
LoginActivity はユーザーの認可を処理し、ユーザーに最初に表示される画面になります。WebAuthProvider を初期化して認証を開始するための login() メソッドを作成します。WebAuthProvider には、正しい scheme、、およびスコープを指定してください。この実装では次を使用します。
- scheme:
demo - audience:
https://api.exampleco.com/timesheets (Node.JS API) - response_type:
code - scope:
create:timesheets read:timesheets openid profile email offline_access。これらのスコープにより、Node.JS API に対してPOSTとGETを実行できるほか、ユーザープロフィールと を取得できます。
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 にリダイレクトされます。
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));
}
});
}
}
認証情報を保存する
CredentialsManager と、保存先として SharedPreferences を使用します。
login() メソッドで WebAuthProvider を初期化する前に、CredentialsManager を作成できます。AuthenticationAPIClient を CredentialsManager に渡すと、有効期限が切れた場合に を更新できるようになります。
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()メソッドを更新します。
// ...
@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 で使用する、シンプルなユーザーモデルを作成します。
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 を使用します。
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() メソッドを更新して、 を取得し、JWTDecode.Android ライブラリを使用してトークンからユーザープロファイルを取り出します。続いて、そのユーザープロファイルを UserProfileManager に保存します。
// ...
@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() を追加します。
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 も忘れずに更新してください。
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 オブジェクトに保存できるようにします。
// ...
@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));
}
// ...
スコープに基づいて承認メニューを表示する
approve:timesheets スコープが付与されているユーザーにのみ表示されるようにする必要があります。
以下は、ユーザーが approve:timesheets スコープを持っているかどうかを BaseActivity クラスで確認し、その結果に応じて承認アクティビティを表示するメニュー項目の表示/非表示を設定するコードです。
// ...
@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 を追加します:
<activity android:name="com.auth0.samples.activities.TimeSheetActivity" />
Timesheets Activity のレイアウトを作成する
TimeSheetsActivity 用のレイアウト timesheet_activity.xml を作成します。
<?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 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 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>
タイムシートモデルを作成する
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;
}
}
TimeSheetAdapter を作成する
TimeSheetAdapterは、タイムシートエントリの配列を受け取り、TimeSheetActivityのListViewに反映するユーティリティクラスです。
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;
}
}
Timesheet Activity を作成する
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 ウィジェットのナビゲーション機能を処理します。
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 レイアウト、そして Toolbar ナビゲーション用の user_action_menu.xml を作成します。このビューには、ユーザーの名前、メールアドレス、プロフィール画像が表示されます。
マニフェストを更新する
アプリのAndroidManifest.xml を開き、UserActivity を追加します。
<activity android:name="com.auth0.samples.activities.UserActivity" />
ユーザーアクティビティのレイアウトを作成する
ImageView と、ユーザーの名前およびメールアドレス用の TextViews を含む、UserActivity のレイアウト user_activity.xml を作成します。
<?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 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からプロフィール画像を読み込む
AsyncTask を継承し、バックグラウンドで実行するタスクを作成します。
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() メソッドが、Toolbarウィジェットのナビゲーション機能を処理します。
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;
}
}
ステップ 7. 新しいタイムシート用フォーム
FormActivityとレイアウトを作成します。
マニフェストを更新する
アプリのAndroidManifest.xml を開き、FormActivity を追加します。
<activity android:name="com.auth0.samples.activities.FormActivity" />
フォームアクティビティのレイアウトを作成する
EditText、作業日の指定には DatePicker を使用した form_activity.xml レイアウトを作成します。
<?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 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>
フォームアクティビティを作成する
@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 ウィジェットのナビゲーション機能を処理します。
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;
}
}
アプリをテストする
- ターミナルで API のディレクトリに移動し、
node serverコマンドを実行して API を起動します。 - Android Studio でモバイルアプリを開き、Run ボタンを押します。
- Nexus 5X API 23 の仮想デバイスを選択します。
- エミュレーターでモバイルアプリが起動したら、ユーザーとしてログインし、実行中の Node.JS API を使用してタイムシートエントリを作成・表示できます。