Android用モバイルアプリの実装方法(モバイルアプリとAPI)
この文書はモバイルおよびAPIアーキテクチャシナリオの一部であり、Androidでモバイルアプリケーションを実装する方法を説明します。実装したソリューションについての情報は、シナリオを参照してください。
1.アプリケーションのセットアップ方法
サンプルプロジェクト
始めるには、このチュートリアル固有のサンプルプロジェクトをダウンロードします。
- Android Studio 2.3
- Android SDK 25
- Emulator - Nexus 5X - Android 6.0
依存関係の設定
この実装ではアプリのbuild.gradle
ファイル内で以下の依存関係を使用します。
Auth0.Android:このパッケージは、ユーザーを認証するためにAuth0との統合を可能にします。
OkHttp:このパッケージはNode.JS APIに要求を行うためのHTTPアプリケーションを提供します。
JWTDecode.Android:このパッケージはJWTのデコードを支援します。
AppCompat:このパッケージは、アクティビティ内でのナビゲーションにおいてツールバーウィジェットの使用を可能にします。
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'
}
Was this helpful?
マニフェストの更新
アプリケーションのAndroidManifest.xml
を開き、インターネット権限を追加します。
<uses-permission android:name="android.permission.INTERNET" />
Was this helpful?
アプリケーションの詳細も更新して、ツールバーウィジェットを活用するようにします。
<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>
Was this helpful?
規定値の設定
Auth0 Client ID, Auth0ドメイン、およびAPIのURLを /res/values/strings.xml
にあるstrings.xml
リソースに設定してください。
<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>
Was this helpful?
この実装ではアプリケーションパッケージ内に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
を開き、ログインアクティビティ
を追加します。
<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>
Was this helpful?
ログインアクティビティの作成
ログインアクティビティ
ログイン()メソッドを作成して、 WebAuthProviderを初期化し、認可を開始します。WebAuthProvider
に必ず正確なスキーム、オーディエンス、およびスコープを提供してください。この実装では以下の識別子を使用します。
scheme:
demo
:audience:
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
要求を行うことができ、ユーザープロフィールやリフレッシュトークンを取得できます。
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(
// ...
);
}
Was this helpful?
ログイン()
メソッドで認証が成功すると、ユーザーを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));
}
});
}
}
Was this helpful?
資格情報の保存
ログイン後に受け取った資格情報を保存するためにAuth0.AndroidライブラリのCredentialsManager
とSharedPreferencesを使用します。
ログイン()
メソッドでWebAuthProvider
を初期化する前に、CredentialsManager
を作成できます。CredentialsManager
にAuthenticationAPIClient
を渡すことで、期限切れのアクセストークンをリフレッシュできます。
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)
// ...
}
Was this helpful?
認証成功後に資格情報がCredentialsManager
を通じて保存されるようにログイン()
メソッドを更新してください。
// ...
@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));
// ...
}
Was this helpful?
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;
}
}
Was this helpful?
ユーザープロファイルの保存
ユーザープロファイル情報の保存を管理するために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();
}
}
Was this helpful?
続いてログインアクティビティ
の ログイン()
メソッドを更新してIDトークンを取得し、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));
}
// ...
Was this helpful?
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);
}
}
Was this helpful?
また、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();
}
}
Was this helpful?
次に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));
}
// ...
Was this helpful?
スコープに基づいて承認メニューを表示する
これで、ユーザーに特定のスコープが付与されているかどうかの情報に基づいて、特定のUI要素を表示することができます。例えば、approve:timesheets
スコープが付与されたユーザーにのみ表示される承認メニュー項目があります。
以下は、BaseActivity
クラスのコードで、ユーザーが approve:timesheets
スコープを持っているかどうかをチェックし、それに基づいて承認アクティビティを表示するメニュー項目の可視性の状態を設定します。
// ...
@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);
}
// ...
Was this helpful?
5.APIの呼び出し
マニフェストの更新
アプリのAndroidManifest.xml
を開き、 TimeSheetActivity
を追加します。
<activity android:name="com.auth0.samples.activities.TimeSheetActivity" />
Was this helpful?
TimeSheetActivityのレイアウトを構成する
次に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>
Was this helpful?
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>
Was this helpful?
TimeSheetActivity
のツールバーナビゲーションのために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>
Was this helpful?
タイムシートモデルを作成する
タイムシートデータをビューで扱うためのモデルを作成します。
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;
}
}
Was this helpful?
タイムシートアダプターを作成する
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;
}
}
Was this helpful?
タイムシートアクティビティの作成
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(
)メソッドはツールバーウィジェットのナビゲーション機能を処理します。
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;
}
}
Was this helpful?
6.ユーザープロファイルを表示する
ログインしているユーザーのプロファイルを表示するには、UserActivity
を作成し、それに対応するuser_activity.xml
レイアウトと、ツールバーナビゲーション用のuser_action_menu.xml
を作成します。ビューは、ユーザーの名前、メールアドレス、およびプロファイル写真を表示します。
マニフェストの更新
アプリのAndroidManifest.xml
を開き、UserActivity
を追加します。
<activity android:name="com.auth0.samples.activities.UserActivity" />
Was this helpful?
ユーザーアクティビティレイアウトを作成する
次に、UserActivity
のレイアウトであるuser_activity.xml
を作成します。これには,ユーザーのプロファイル写真用のImageView
と、ユーザーの名前とメールアドレス用のTextViews
が含まれます。
<?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>
Was this helpful?
続いて UserActivity
ツールバー用に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>
Was this helpful?
URLからプロファイル写真を読み込む
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);
}
}
Was this helpful?
ユーザーアクティビティを作成する
onCreate()
メソッドで、UserProfileManager
からユーザー情報を取得し、ビューに値を設定します。前と同様に、onCreateOptionsMenu()
とonOptionsItemSelected()
メソッドは、ツールバーウィジェットのナビゲーション機能を処理します。
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;
}
}
Was this helpful?
7.新しいタイムシートのためのフォーム
次に、新しいタイムシートエントリを作成するためのFormActivity
とレイアウトを作成します。
マニフェストの更新
アプリのAndroidManifest.xml
を開き、FormActivity
を追加します。
<activity android:name="com.auth0.samples.activities.FormActivity" />
Was this helpful?
フォームアクティビティのレイアウトの作成
form_activity.xml
レイアウトを作成します。これにはプロジェクト名と作業時間の入力用の EditText
および作業日用のDatePicker
が含まれます。
<?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>
Was this helpful?
また、FormActivity
のツールバー用に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>
Was this helpful?
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()
メソッドはツールバーウィジェットのナビゲーション機能を処理します。
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;
}
}
Was this helpful?
アプリのテスト
続行する前に、 Node.js APIを実装していることを確認してください。
APIのディレクトリにターミナルで移動し、
node server
コマンドを入力してAPIを起動します。次に、Android Studioでモバイルアプリを開き、[Run(実行)]ボタンを押します。
Nexus 5X API 23の仮想デバイスを選択します。
エミュレーターがモバイルアプリを読み込んだら、ユーザーにログインし、その後、実行中のNode.js APIからタイムシートエントリを作成および表示できます。
これで作業完了です。完了です。