Androidライブラリに依存する通信部品クラスをJavaブジェクトで開発・実行する方法について解説します。
バックグラウンド スレッドで実行する通信部品クラスはレスポンスを受け取った後に、UIスレッドでウィジットを更新するためにHandlerというAndroid固有クラスを使用する必要があります。
java-health-care-example/
└── android
├── app
│ └── Application.java
├── content
│ └── res
│ └── AssetManager.java
├── os
│ └── Handler.java
└── util
└── Log.java
package android.app;
public class Application {
public Application() {
onCreate();
}
public void onCreate() {
// No ope
}
}
package android.content.res;
import android.util.Log;
import java.io.*;
import java.nio.file.Paths;
/**
* android.content.AssetManagerの擬似クラス
* [res]ディレクトリのリクエスト用JSONファイルを読み込み、
*/
public class AssetManager {
private static final String TAG = "AssetManager";
// Development environ: Ubuntu18.04
// ~/project/java-workspaces/java-health-care-example/resources
private static final String HOME = System.getenv("HOME");
private static final String RESOURCE_PATH = Paths.get(HOME,
"project", "java-workspaces", "java-health-care-example", "resources").toString();
public AssetManager() {
}
public InputStream open(String filename) throws IOException {
File absFile = Paths.get(RESOURCE_PATH, filename).toFile();
Log.d(TAG, "file: " + absFile);
return new FileInputStream(absFile);
}
public void save(byte[] imageData, String filename) throws IOException {
File absFile = Paths.get(RESOURCE_PATH, filename).toFile();
Log.d(TAG, "saved: " + absFile);
try (FileOutputStream out = new FileOutputStream(absFile)) {
out.write(imageData);
}
}}
package android.os;
/**
* android.os.Handlerの擬似クラス
*/
public class Handler {
public void post(Runnable runnable) {
// Javaアプリケーシヨンでは何もしない
try {
runnable.run();
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
package android.util;
/**
* android.util.Logの擬似クラス
*
* System.out.printlin()でコンソールに出力
*/
public class Log {
private Log() {}
public static void d(String tag, String message) {
System.out.println("D/" + tag + ":" + message);
}
public static void w(String tag, String message) {
System.out.println("W/" + tag + ":" + message);
}
public static void e(String tag, String message) {
System.out.println("E/" + tag + ":" + message);
}
}
ほとんど上記本家サイトからコードを流用し作成しましたが、残念ながらこのサイトのコード例は完成形にはなっていません。 オフィシャルのサンプルにもこのサイトの内容に該当するサンプルは確認できませんでした。 ※このサイトの実装は ViewModel を使った例になっていますが、当方はViewModelを使わない通常のJavaクラスで実装しています。
実装方法の詳細は上記サイトに日本語で説明されているのでご覧になってください。※JavaDocの無いものはほとんどそのまま上記サイトからの流用です
java-health-care-example/
└── com
└── examples
└── android
└── healthcare
├── BuildConfig.java (*) DEBUG定義 ※Andorid Studioでは自動生成、Javaプロジェクトでは手動で作成
├── HealthcareApplication.java (*) Androidアプリケーションクラス
├── MainPostData.java (*) データ登録POSTリクエストメインクラス
├── MainGetData.java (*) 登録済みデータ取得GETリクエストメインクラス
├── functions
│ └── MyLogging.java (*) DEBUGログ出力Functionインターフェース
└── tasks
├── GetCurrentDataRepository.java (*) 登録済みデータ取得用リポジトリクラス(HTTPクライアント)
├── HealthcareRepository.java (*) ベースリポジトリクラス(HTTPクライアント)
├── RepositoryCallback.java (*) レスポンス結果を画面に表示するためのコールバック
├── ResisterDataRepository.java (*) データ登録用リポジトリクラス(HTTPクライアント)
└── Result.java (*) ネットワークリクエストのレスポンスをモデル化したクラス
package com.examples.android.healthcare.functions;
import android.util.Log;
import com.examples.android.healthcare.BuildConfig;
import java.util.function.BiConsumer;
public class MyLogging {
@FunctionalInterface
public interface LogParamsConsumer<S, S1, T> {
public void print(String tag, String format, Object... params);
}
// DEBUG Log Function
public static BiConsumer<String, String> DEBUG_OUT = (tag, output) -> {
if (BuildConfig.DEBUG) { //■■ デバックビルドならDEBUGログを出力 ※リリースビルドなら出力されない ■■
Log.d(tag, output);
}
};
// DEBUG Log Function
public LogParamsConsumer<String, String, Object[]> DEBUG_OUT_PARAMS = (tag, format, params) -> {
if (BuildConfig.DEBUG) {
String output = String.format(format, params);
Log.d(tag, output);
}
};
}
package com.examples.android.healthcare.tasks;
import com.examples.android.healthcare.data.ResponseStatus;
public abstract class Result<T> {
private Result() {
}
public static final class Success<T> extends Result<T> {
private final T data;
public Success(T data) {
this.data = data;
}
public T get() {return data; }
}
public static final class Warning<T> extends Result<T> {
private final ResponseStatus status;
public Warning(ResponseStatus status) {
this.status = status;
}
public ResponseStatus getResponseStatus() { return status; }
}
public static final class Error<T> extends Result<T> {
private final Exception exception;
public Error(Exception exception) {
this.exception = exception;
}
public Exception getException() { return exception; }
}
}
package com.examples.android.healthcare.tasks;
public interface RepositoryCallback<T> {
void onComplete(Result<T> result);
}
package com.examples.android.healthcare.tasks;
import android.os.Handler; // Android依存クラス ※Java環境ではスタブを参照
import android.util.Log; // 同上
import com.google.gson.JsonParseException;
import com.examples.android.healthcare.data.GetCurrentDataResult;
import com.examples.android.healthcare.data.RegisterResult;
import com.examples.android.healthcare.data.ResponseStatus;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import static com.examples.android.healthcare.functions.MyLogging.DEBUG_OUT;
/**
* HTTPリクエスト実行リポジトリクラス
* @param <T> レスポンス用Javaクラス
*/
public abstract class HealthcareRepository<T> {
private static final String TAG = "HealthcareRepository";
public HealthcareRepository() {}
/**
* GETリクエスト生成メソッド
* @param pathIdx パス用インデックス ※サブクラスで複数のGETリクエストパス定義
* @param baseUrl パスを含まないURL (Wifi | Mobile)
* @param requestParameter リクエストパラメータ
* @param headers リクエストヘッダー
* @param executor スレッドエクゼキューター
* @param handler Android Handlerオブジェクト
* @param callback Activity(Fragment)が結果を受け取るコールバック
*/
public void makeGetRequest(
int pathIdx,
String baseUrl,
String requestParameter,
Map<String, String> headers,
ExecutorService executor,
Handler handler,
final RepositoryCallback<T> callback) {
executor.execute(() -> {
try {
String requestUrl = baseUrl + getRequestPath(pathIdx) + requestParameter;
Result<T> result =
getRequest(requestUrl, headers);
// 200, 4xx - 50x系
notifyResult(result, callback, handler);
} catch (Exception e) {
// サーバー側のレスポンスBUGか, Android側のBUG想定
Result<T> errorResult = new Result.Error<>(e);
notifyResult(errorResult, callback, handler);
}
});
}
/**
* POSTリクエスト(登録・更新)生成メソッド
* @param pathIdx パス用インデックス ※サブクラスで複数のGETリクエストパス定義
* @param baseUrl パスを含まないURL (Wifi | Mobile)
* @param headers リクエストヘッダー
* @param executor スレッドエクゼキューター
* @param handler Android Handlerオブジェクト
* @param callback Activity(Fragment)が結果を受け取るコールバック
*/
public void makeRegisterRequest(
int pathIdx,
String baseUrl,
String jsonData,
Map<String, String> headers,
ExecutorService executor,
Handler handler,
final RepositoryCallback<T> callback) {
executor.execute(() -> {
try {
String requestUrl = baseUrl + getRequestPath(pathIdx);
Result<T> result =
postRegisterDataRequest(requestUrl, headers, jsonData);
notifyResult(result, callback, handler);
} catch (Exception e) {
Result<T> errorResult = new Result.Error<>(e);
notifyResult(errorResult, callback, handler);
}
});
}
/**
* GETリクエスト実行メソッド
* @param requestUrl リクエストURL
* @param requestHeaders リクエストヘッダー
* @return サーバーからのレスポンスを対応するクラスのオブジェクト
*/
private Result<T> getRequest(
String requestUrl, Map<String, String> requestHeaders) {
HttpURLConnection conn = null;
try {
URL url = new URL(requestUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json;");
for (String key : requestHeaders.keySet()) {
conn.setRequestProperty(key, requestHeaders.get(key));
}
// Check response code: allow 200 only.
int respCode = conn.getResponseCode();
DEBUG_OUT.accept(TAG, "ResponseCode:" + respCode);
if (respCode == HttpURLConnection.HTTP_OK) {
String respText = getResponseText(conn.getInputStream());
T result = parseResultJson(respText);
return new Result.Success<>(result);
} else {
// 4xx - 50x
// Flaskアプリからはエラーストリームが生成される
String respText = getResponseText(conn.getErrorStream());
DEBUG_OUT.accept(TAG, "NG: " + respText);
// ウォーニング時のJSONはデータ部が存在しないのでウォーニング専用ハースを実行
T result = parseWarningJson(respText);
// ResponseStatusのみ, GET用Dataクラス == null
ResponseStatus status = ((GetCurrentDataResult) result).getStatus();
DEBUG_OUT.accept(TAG, "NG.ResponseStatus: " + respText);
return new Result.Warning<>(status);
}
} catch (Exception ie) {
Log.w(TAG, ie.getLocalizedMessage());
return new Result.Error<>(ie);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
/**
* POSTリクエスト(登録・更新)実行メソッド
* @param requestUrl リクエストURL
* @param requestHeaders リクエストヘッダー
* @param jsonData JSONデータ(文字列)
* @return サーバーからのレスポンスを対応するクラスのオブジェクト
*/
private Result<T> postRegisterDataRequest(
String requestUrl, Map<String, String> requestHeaders, String jsonData) {
HttpURLConnection conn = null;
try {
URL url = new URL(requestUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json;");
// POSTデータ有り(JSON)
conn.setDoOutput(true);
// レスポンス有り
conn.setDoInput(true);
conn.setChunkedStreamingMode(0);
// ヘッダー設定
for (String key : requestHeaders.keySet()) {
conn.setRequestProperty(key, requestHeaders.get(key));
}
// リクエストデータ用出力ストリーム
OutputStream output = new BufferedOutputStream(conn.getOutputStream());
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(output, StandardCharsets.UTF_8));
writer.write(jsonData);
writer.flush();
// Check response code: allow 200 only.
int respCode = conn.getResponseCode();
DEBUG_OUT.accept(TAG, "ResponseCode:" + respCode);
if (respCode == HttpURLConnection.HTTP_OK) {
String respText = getResponseText(conn.getInputStream());
T result = parseResultJson(respText);
return new Result.Success<>(result);
} else {
// 4xx - 50x: ResultStatus, Dataクラス == null
// Flaskアプリからはエラーストリームが生成される
String respText = getResponseText(conn.getErrorStream());
DEBUG_OUT.accept(TAG, "NG: " + respText);
// ウォーニング時のJSONはデータ部が存在しないのでウォーニング専用ハースを実行
T result = parseWarningJson(respText);
// ResponseStatusのみ, Post用Dataクラス == null
ResponseStatus status = ((RegisterResult) result).getStatus();
DEBUG_OUT.accept(TAG, "NG.ResponseStatus: " + respText);
return new Result.Warning<>(status);
}
} catch (Exception ie) {
// パースエラーまたはIO例外
Log.w(TAG, ie.getLocalizedMessage());
return new Result.Error<>(ie);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
/**
* UI側のコールバックに結果(Javaオブジェクト)をセットする
* @param result レスポンスオブジェクト
* @param callback UI側コールバック
* @param handler Androidハンドラー
*/
private void notifyResult(final Result<T> result,
final RepositoryCallback<T> callback,
final Handler handler) {
handler.post(() -> callback.onComplete(result));
}
/**
*
* @param pathIdx パスインデックス (1 - m)
* @return サブクラスが提供するパス
*/
public abstract String getRequestPath(int pathIdx);
/**
* 入力ストリームからJSON文字列を取得
* @param is 入力ストリーム
* @return JSON文字列
* @throws IOException IO例外
*/
public abstract String getResponseText(InputStream is) throws IOException;
/**
* HTTP 200(OK) レスポンス時のJSON文字列をパースしてJavaオブジェクトを生成
* @param jsonText JSON文字列を
* @return サブグラスが定義するJavaオブジェクトを生成
* @throws JsonParseException GSONのパース例外
*/
public abstract T parseResultJson(String jsonText) throws JsonParseException;
/**
* HTTP 4xx, 50x系レスポンス時のJSON文字列をパースしてJavaオブジェクトを生成
* @param jsonText ウォーニング用JSON文字列
* @return サブグラスが定義するウォーニング用Javaオブジェクトを生成
* @throws JsonParseException GSONのパース例外
*/
public abstract T parseWarningJson(String jsonText) throws JsonParseException;
}
public class LoginRepository {
// ...
public void makeLoginRequest(
final String jsonBody,
final RepositoryCallback<LoginResponse> callback,
final Handler resultHandler,
) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
notifyResult(result, callback, resultHandler);
} catch (Exception e) {
Result<LoginResponse> errorResult = new Result.Error<>(e);
notifyResult(errorResult, callback, resultHandler);
}
}
});
}
private void notifyResult(
final Result<LoginResponse> result,
final RepositoryCallback<LoginResponse> callback,
final Handler resultHandler
) {
resultHandler.post(new Runnable() {
@Override
public void run() {
callback.onComplete(result);
}
});
}
}
package com.examples.android.healthcare.tasks;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.examples.android.healthcare.data.RegisterResult;
import com.examples.android.healthcare.data.ResponseStatus;
import com.examples.android.healthcare.data.ResponseWarningStatus;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* AppTopFragment用データ登録(更新)リポジトリクラス
*/
public class ResisterDataRepository extends HealthcareRepository<RegisterResult> {
// パス配列: 登録時, 更新時
private static final String[] URL_PATHS = {"/register", "/update"};
public ResisterDataRepository() {}
@Override
public String getRequestPath(int pathIdx) {
return URL_PATHS[pathIdx];
}
@Override
public String getResponseText(InputStream is) throws IOException {
StringBuilder sb;
try (BufferedReader bf = new BufferedReader
(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
sb = new StringBuilder();
while ((line = bf.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
/**
* 正常時のレスポンスオブジェクトを取得する
* <pre>HTTP OK時にサーバーが返却するレスポンス例
{"data": {"emailAddress": "user1@example.com", "measurementDay": "2023-02-13"},
"status": {"code": 0, "message": "OK"}}
* </pre>
* @param jsonText JSON文字列
* @return レスポンスオブジェクト
* @throws JsonParseException パース例外
*/
@Override
public RegisterResult parseResultJson(String jsonText) throws JsonParseException {
Gson gson = new Gson();
return gson.fromJson(jsonText, RegisterResult.class);
}
/**
* ウォーニング時のレスポンスオブジェクトを取得する
* <pre>ウォーニング時にサーバーが返却するレスポンス例
{"status": {"code": 400,"message": "461,User is not found."}}
* </pre>
* @param jsonText ウォーニング用JSON文字列
* @return レスポンスオブジェクト<br/>
* ResponseStatusオブジェクトのみがセットされDataオブジェクトはnullがセットされる
* @throws JsonParseException パース例外
*/
@Override
public RegisterResult parseWarningJson(String jsonText) throws JsonParseException {
Gson gson = new GsonBuilder().serializeNulls().create();
// ResponseStatusに"status"タグ含めたResponseStatusのラップクラス
// {"status": {"code": 400,"message": "461,User is not found."}}部分を受取るクラス
ResponseWarningStatus warningStatus = gson.fromJson(jsonText, ResponseWarningStatus.class);
// {"code": 400,"message": "461,User is not found."} 部分を受取るクラス
ResponseStatus status = warningStatus.getStatus();
return new RegisterResult(null, status);
}
}
package com.examples.android.healthcare.tasks;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.examples.android.healthcare.data.GetCurrentDataResult;
import com.examples.android.healthcare.data.ResponseStatus;
import com.examples.android.healthcare.data.ResponseWarningStatus;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* AppTopFragment用登録済みデータ取得リポジトリクラス
*/
public class GetCurrentDataRepository extends HealthcareRepository<GetCurrentDataResult> {
// GETリクエストパス
private static final String URL_PATH = "/getcurrentdata";
@Override
public String getRequestPath(int pathIdx) {
return URL_PATH;
}
@Override
public String getResponseText(InputStream is) throws IOException {
StringBuilder sb;
try (BufferedReader bf = new BufferedReader
(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
sb = new StringBuilder();
while ((line = bf.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
/**
* 登録済みデータオブジェクトを取得する
* <pre>HTTP OK時にサーバーが返却するレスポンス例 ※一部省略
{"data": {"emailAddress": "sapporo@examples.com", "healthcareData": { "bloodPressure": {
"eveningMax": 124, "eveningMeasurementTime": "22:20", "eveningMin": 72, "eveningPulseRate": 59,
"morningMax": 112, "morningMeasurementTime": "06:45", "morningMin": 67, "morningPulseRate": 65 },
...一部省略...
},"walkingCount": {counts": 8576}},
"measurementDay": "2023-02-01",
"weatherData": {"weatherCondition": {"condition": "曇りのち雪"}}},
"status": {"code": 0, "message": "OK"}}
* </pre>
* @param jsonText JSON文字列を
* @return 登録済みデータオブジェクト
* @throws JsonParseException パース例外
*/
@Override
public GetCurrentDataResult parseResultJson(String jsonText) throws JsonParseException {
Gson gson = new Gson();
return gson.fromJson(jsonText, GetCurrentDataResult.class);
}
/**
* ウォーニング時のレスポンスオブジェクトを取得する
* <pre>ウォーニング時にサーバーが返却するレスポンス例
{"status": {"code": 400,"message": "461,User is not found."}}
* </pre>
* @param jsonText ウォーニング用JSON文字列
* @return レスポンスオブジェクト<br/>
* ResponseStatusオブジェクトのみがセットされDataオブジェクトはnullがセットされる
* @throws JsonParseException パース例外
*/
@Override
public GetCurrentDataResult parseWarningJson(String jsonText) throws JsonParseException {
Gson gson = new GsonBuilder().serializeNulls().create();
ResponseWarningStatus warningStatus = gson.fromJson(jsonText, ResponseWarningStatus.class);
ResponseStatus status = warningStatus.getStatus();
return new GetCurrentDataResult(null, status);
}
}
package com.examples.android.healthcare;
import android.app.Application; // スタブ
import android.content.res.AssetManager; // スタブ
import android.os.Handler; // スタブ
import android.util.Log; // スタブ
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.examples.android.healthcare.functions.MyLogging.DEBUG_OUT;
public class HealthcareApplication extends Application {
private static final String LOG_TAG = "HealthcareApplication";
// Json file in assets for request.
private static final String REQUEST_INFO_FILE = "request_info.json";
private Map<String, String> mRequestUrls;
private Map<String, String> mRequestHeaders;
public ExecutorService mEexecutor = Executors.newFixedThreadPool(1);
// Handerだけは Androidとは異なりダミーのコンストラクタで代用 ※何の処理もしないクラス
public Handler mHandler = new Handler();
// ■■ Androidアプリのプロジェクトにコピーするときには下記コードに置き換える必要がある
// public Handler mHandler = HandlerCompat.createAsync(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
try {
loadRequestConf();
} catch (Exception e) {
// ここには来ない想定
Log.e(LOG_TAG, e.getLocalizedMessage());
}
}
public Map<String, String> getmRequestUrls() {
return mRequestUrls;
}
public Map<String, String> getRequestHeaders() {
return mRequestHeaders;
}
private void loadRequestConf() throws IOException {
AssetManager am = new AssetManager();
Gson gson = new Gson();
Type typedMap = new TypeToken<Map<String, Map<String, String>>>() {
}.getType();
DEBUG_OUT.accept(LOG_TAG, "typedMap: " + typedMap);
// gson.fromJson() thorows JsonSyntaxException, JsonIOException
Map<String, Map<String, String>> map = gson.fromJson(
new JsonReader(new InputStreamReader(am.open(REQUEST_INFO_FILE))), typedMap);
mRequestUrls = map.get("urls");
mRequestHeaders = map.get("headers");
DEBUG_OUT.accept(LOG_TAG, "RequestUrls: " + mRequestUrls);
DEBUG_OUT.accept(LOG_TAG, "RequestHeaders: " + mRequestHeaders);
}
}
private String getJsonText() {
// JavaプロジェクトならJSONファイルから取得
// Androidプロジェクトなら全ての入力ウィジットから取得
}
// 登録データJSONテキスト取得
String jsonText = getJsonText();
// アプリケーションオブジェクト生成
HealthcareApplication app = new HealthcareApplication();
// アプリケーションオブジェクトからリクエスト情報取得
Map<String, String> urlMap = app.getmRequestUrls(); // ベースURLパスを保持するマップ
Map<String, String> headers = app.getRequestHeaders(); // リクエストヘッダー
String requestUrl = urlMap.get(RequestDevice.WIFI.toString()); // リクエストベースURLパス取得
// データ登録POSTリクエスト用リポジトリ生成
HealthcareRepository<RegisterResult> repository = new ResisterDataRepository();
int urlNum = 0; // 0:登録(register), 1:更新(update)
repository.makeRegisterRequest(urlNum, requestUrl, jsonText, headers,
app.mEexecutor/*スレッドプール*/, app.mHandler/*ハンドラー*/, (result) -> {
if (result instanceof Result.Success) {
// レスポンスOK時の処理
// Androidの場合はステータスに登録OKを表示
} else if (result instanceof Result.Warning) {
// ウォーニングレスポンス時の処理(40x, 501)
// ウォーニングステータスオブジェクト取得
ResponseStatus status =
((Result.Warning>) result).getResponseStatus();
// Androidの場合はステータスにエラー内容を表示
} else {
// 例外オブジェクト取得
Exception exception = ((Result.Error>) result).getException();
// 例外発生時の処理 (主にサーバーアプリ側のバグかAndroidアプリ側のバグ[JSONパースエラー等])
// Androidの場合は例外メッセージをダイアログに表示
}
});
// 登録済みデータの主キー項目
String emailAddress = "user1@examples.com";
String pastDay = "2023-03-10";
// アプリケーションオブジェクト生成
HealthcareApplication app = new HealthcareApplication();
// アプリケーションオブジェクトからリクエスト情報取得
Map<String, String> urlMap = app.getmRequestUrls();
Map<String, String> headers = app.getRequestHeaders();
String requestUrl = urlMap.get(RequestDevice.WIFI.toString());
// 登録済みデータ取得GETリクエスト用リポジトリ生成
HealthcareRepository<GetCurrentDataResult> repository = new GetCurrentDataRepository();
// リクエストパラメータ取得(URLエンコード)
String requestParams = AppTopUtil.getRequestParams(emailAddress, pastDay);
repository.makeGetRequest(0, requestUrl, requestParams, headers,
app.mEexecutor, app.mHandler, (result) -> {
if (result instanceof Result.Success) {
// OKレスポンスオブジェクト取得
GetCurrentDataResult dataResult =
((Result.Success<GetCurrentDataResult>) result).get();
// 登録済みデータ取得
RegisterData data = dataResult.getData();
// Androidの場合は登録済みデータをウィジットに設定
} else if (result instanceof Result.Warning) {
// ウォーニングステータスオブジェクト取得
ResponseStatus status =
((Result.Warning<?>) result).getResponseStatus();
// Androidの場合はステータスにエラー内容を表示
} else {
// 例外オブジェクト取得
Exception exception = ((Result.Error<?>) result).getException();
// 例外発生時の処理 (主にサーバーアプリ側のバグかAndroidアプリ側のバグ[JSONパースエラー等])
// Androidの場合は例外メッセージをダイアログに表示
}
});
package com.examples.android.healthcare;
import android.util.Log;
import com.examples.android.healthcare.constants.RequestDevice;
import com.examples.android.healthcare.data.GetCurrentDataResult;
import com.examples.android.healthcare.data.RegisterData;
import com.examples.android.healthcare.data.ResponseStatus;
import com.examples.android.healthcare.functions.AppTopUtil;
import com.examples.android.healthcare.tasks.GetCurrentDataRepository;
import com.examples.android.healthcare.tasks.HealthcareRepository;
import com.examples.android.healthcare.tasks.Result;
import java.util.Map;
import static com.examples.android.healthcare.functions.MyLogging.DEBUG_OUT;
// 健康管理データ登録Androidアプリ用通信部品のテスト用コード
public class MainGetData {
static final String TAG = "MainGetData";
// 登録済みデータの主キー
static final String emailAddress = "user1@examples.com";
static final String pastDay = "2023-03-10";
public static void main(String[] args) {
HealthcareApplication app = new HealthcareApplication();
// ローカルネットワーク
Map<String, String> urlMap = app.getmRequestUrls();
Map<String, String> headers = app.getRequestHeaders();
String requestUrl = urlMap.get(RequestDevice.WIFI.toString());
DEBUG_OUT.accept(TAG, "requestUrl: "+ requestUrl);
try {
// GETリクエスト送信: 登録済みデータの取得
HealthcareRepository<GetCurrentDataResult> repository = new GetCurrentDataRepository();
// リクエストパラメータ: 主キー項目(メールアドレス, 測定日付)
String requestParams = AppTopUtil.getRequestParams(emailAddress, pastDay);
repository.makeGetRequest(0, requestUrl, requestParams, headers,
app.mEexecutor, app.mHandler, (result) -> {
if (result instanceof Result.Success) {
GetCurrentDataResult dataResult =
((Result.Success<GetCurrentDataResult>) result).get();
RegisterData data = dataResult.getData();
DEBUG_OUT.accept(TAG, "responseResult: " + dataResult);
} else if (result instanceof Result.Warning) {
ResponseStatus status =
((Result.Warning<?>) result).getResponseStatus();
Log.w(TAG, status.toString());
} else if (result instanceof Result.Error) {
// 例外メッセージ
Exception exception = ((Result.Error<?>) result).getException();
Log.w(TAG, "GET error:" + exception.toString());
}
});
} finally {
//■■ Javaアプリでは一回きりの実行なのでシャットダウンでプロセスを終了させる ■■
app.mEexecutor.shutdownNow();
}
}
}
/**
* GETリクエストで該当するデータを取得する
* @param emailAddress メールアドレス
* @param pastDay 過去の測定日付
*/
private void sendGetCurrentDataRequest(String emailAddress, String pastDay) {
RequestDevice device = NetworkUtil.getActiveNetworkDevice(getContext());
if (device == RequestDevice.NONE) {
showDialogNetworkUnavailable();
return;
}
showActionBarGetting(device);
HealthcareApplication app = (HealthcareApplication) requireActivity().getApplication();sendGetCurrentDataRequest
String requestUrl = app.getmRequestUrls().get(device.toString());
Map<String, String> headers = app.getRequestHeaders();
// GETリクエスト送信: 登録済みデータの取得
HealthcareRepository<GetCurrentDataResult> repository = new GetCurrentDataRepository();
String requestUrlWithPath = requestUrl + repository.getRequestPath(0);
// リクエストパラメータ: 主キー項目(メールアドレス, 測定日付)
String requestParams = AppTopUtil.getRequestParams(emailAddress, pastDay);
repository.makeGetRequest(0, requestUrl, requestParams, headers,
app.mEexecutor, app.mHandler, (result) -> {
// リクエストURLをAppBarに表示
showActionBarResult(requestUrlWithPath);
// 送信ボタンを戻す
mBtnSend.setEnabled(true);
if (result instanceof Result.Success) {
GetCurrentDataResult dataResult =
((Result.Success<GetCurrentDataResult>) result).get();
RegisterData data = dataResult.getData();
// 古いオブジェクトを破棄する
if (mRegisterDataForUpdate != null) {
mRegisterDataForUpdate = null;
}
// https://www.baeldung.com/java-deep-copy
// 6.3. JSON Serialization With Jackson
Gson gson = new Gson();
// 各入力フィールドに変更があったかどうかを確認するためのオブジェクト
mRegisterDataForUpdate =
gson.fromJson(gson.toJson(data), RegisterData.class);
// ウィジット更新
updateInputWidgetsFromRegisterData(data);
// ステータス更新
showStatus(getString(R.string.message_get_current_ok));
// 更新用モニター開始
startUpdateMonitor();
// 送信ボタンのラベルを"更新"に変更
changePostRequestWithButton(PostRequest.UPDATE);
} else if (result instanceof Result.Warning) {
ResponseStatus status =
((Result.Warning<?>) result).getResponseStatus();
DEBUG_OUT.accept(TAG, "WarningStatus: " + status);
if (status.getCode() == 404) {
// 未登録(404)なら新規登録モードにリセット
mBtnSave.setEnabled(true);
resetToNewRegistrationMode();
}
showWarning(getResponseWarning(status));
} else {
// 例外メッセージをダイアログに表示
Exception exception = ((Result.Error<?>) result).getException();
Log.w(TAG, "GET error:" + exception.toString());
String errorMessage = String.format(
getString(R.string.exception_with_reason),
exception.getLocalizedMessage());
showMessageDialog(getString(R.string.error_response_dialog_title),
errorMessage,"ExceptionFragment");
}
});
}
package com.examples.android.healthcare;
import android.util.Log;
import com.examples.android.healthcare.constants.RequestDevice;
import com.examples.android.healthcare.data.RegisterResult;
import com.examples.android.healthcare.data.ResponseStatus;
import com.examples.android.healthcare.tasks.HealthcareRepository;
import com.examples.android.healthcare.tasks.ResisterDataRepository;
import com.examples.android.healthcare.tasks.Result;
import com.examples.android.healthcare.util.FileUtil;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import static com.examples.android.healthcare.functions.MyLogging.DEBUG_OUT;
// 健康管理データ登録Androidアプリ用通信部品のテスト用コード
public class MainPostData {
static final String TAG = "MainPostData";
// テスト用登録用JSONファイル
static final String JSON_NAME = "register_data_20230311.json";
public static void main(String[] args) {
String jsonFile = Paths.get(Constants.INPUT_DATA_PATH, JSON_NAME).toString();
// 登録用データの読み込み
String jsonText = null;
try {
List<String> lines = FileUtil.readLines(jsonFile);
jsonText = String.join("", lines);
} catch (IOException e) {
Log.e(TAG, "Error: "+ e);
System.exit(1);
}
HealthcareApplication app = new HealthcareApplication();
// ローカルネットワーク
Map<String, String> urlMap = app.getmRequestUrls();
Map<String, String> headers = app.getRequestHeaders();
String requestUrl = urlMap.get(RequestDevice.WIFI.toString());
DEBUG_OUT.accept(TAG, "requestUrl: " + requestUrl);
try {
HealthcareRepository<RegisterResult> repository = new ResisterDataRepository();
int urlNum = 0; // 登録
repository.makeRegisterRequest(urlNum, requestUrl, jsonText, headers,
app.mEexecutor, app.mHandler, (result) -> {
if (result instanceof Result.Success) {
RegisterResult respResult =
((Result.Success<RegisterResult>) result).get();
DEBUG_OUT.accept(TAG, "responseResult: " + respResult);
} else if (result instanceof Result.Warning) {
ResponseStatus status =
((Result.Warning<?>) result).getResponseStatus();
Log.w(TAG, status.toString());
} else {
Exception exception = ((Result.Error<?>) result).getException();
Log.w(TAG, "GET error:" + exception.toString());
}
});
} finally {
// Javaアプリでは一回きりの実行なのでシャットダウンでプロセスを終了させる
app.mEexecutor.shutdownNow();
}
}
}
/**
* 入力ウィジットからJSON文字列を生成し、サーバーに送信する
* POSTリクエスト用JSON文字列生成
* 登録: 全グループ項目のJSON文字列を生成
* 更新: 各グループのうち変更のあった部分を含むJSON文字列を生成
*/
private void sendRegisterData() {
// ネットワークデバイスが無効なら送信しない
RequestDevice device = NetworkUtil.getActiveNetworkDevice(
Objects.requireNonNull(getContext()));
if (device == RequestDevice.NONE) {
showDialogNetworkUnavailable();
return;
}
// メールアドレスチェック
if (TextUtils.isEmpty(getUserEmailWithThisSettings())) {
showConfirmDialogWithEmailAddress();
return;
}
// 必須入力項目チェック
List<String> warnings = checkRequiredInputs(false);
if (!warnings.isEmpty()) {
// メッセージダイアログ表示
String warning = String.join("\n", warnings);
showMessageDialog(getString(R.string.warning_required_dialog_title), warning,
"RequiredDialogFragment");
return;
}
// 確認ダイアログのボタン(OK|CANCEL)押下イベント受信リスナー
ConfirmOkCancelListener listener = new ConfirmOkCancelListener() {
@Override
public void onOk() {
// 送信ボタン不可
mBtnSend.setEnabled(false);
// アクションバーにネットワークデバイス状況を設定
showActionBarGetting(device);
// アプリケーション取得
HealthcareApplication app = (HealthcareApplication) requireActivity().getApplication();
// アプリケーションに保持しているリクエスト情報からネットワーク種別に応じたリクエストURLを取得
String requestUrl = app.getmRequestUrls().get(device.toString());
Map<String, String> headers = app.getRequestHeaders();
// 入力ウィジットからJsonデータ取得
String jsonText;
if (PostRequest.REGISTER.getNum() == mCurrentPostRequest.getNum()) {
jsonText = generateJsonTextForRegist(); //■■ 全ての入力ウィジットからJSON文字列 ■■
} else {
jsonText = generateJsonTextForUpdate(); //■■ 更新された部分のみのJSON文字列 ■■
}
// POST 送信: 登録(0), 更新(1)
int urlNum = mCurrentPostRequest.getNum();
HealthcareRepository<RegisterResult> repository = new ResisterDataRepository();
String requestUrlWithPath = requestUrl + repository.getRequestPath(urlNum);
DEBUG_OUT.accept(TAG, "requestUrlWithPath: " + requestUrlWithPath);
repository.makeRegisterRequest(urlNum, requestUrl, jsonText, headers,
app.mEexecutor, app.mHandler, (result) -> {
// ボタン状態を戻す
mBtnSend.setEnabled(true);
// リクエストURLをAppBarに表示
showActionBarResult(requestUrlWithPath);
if (result instanceof Result.Success) {
String recentJson;
// 登録処理の場合
if (mCurrentPostRequest.equals(PostRequest.REGISTER)) {
// 更新前のプリファレンスから最新登録日付を取得する
String before = getLatestRegisteredDateInPref();
// 測定日付から登録日付を取得
String after = toStringOfTextViewBySelfTag(mInpMeasurementDate);
// 登録日付がプリファレンスの最新登録日付より最新の場合のみ上書き保存
if (AppTopUtil.morelatestInPrefDate(after, before)) {
// 最新登録日の復元用に登録済みJSONをファイル保存
saveJsonToFile(JsonFileSaveTiming.REGISTERED);
}
// 送信成功なら一時保存JSONファイルを削除する
deleteSavedFile();
// 登録: リクエストのJSON文字列をそのまま利用
recentJson = jsonText;
} else {
// 更新: 全入力ウィジットからJSON文字列を生成する
recentJson = generateJsonTextForRegist();
}
// 古いオブジェクトを破棄してから最新の更新用オブジェクトを生成する
if (mRegisterDataForUpdate != null) {
mRegisterDataForUpdate = null;
}
Gson gson = new Gson();
mRegisterDataForUpdate =
gson.fromJson(recentJson, RegisterData.class);
// ステータス更新
String status = String.format(
getString(R.string.msg_post_ok_with_type),
mCurrentPostRequest.getName());
showStatus(status);
// 保存ボタン不可
mBtnSave.setEnabled(false);
// ボタンを更新に変更
if (mCurrentPostRequest.equals(PostRequest.REGISTER)) {
// 送信ボタンを更新に変更してそのまま変更可能にする
changePostRequestWithButton(PostRequest.UPDATE);
}
} else if (result instanceof Result.Warning) {
// ウォーニングメッセージをダイアログに表示
ResponseStatus status =
((Result.Warning<?>) result).getResponseStatus();
DEBUG_OUT.accept(TAG, "WarningStatus: " + status);
// 引数 (1)POSTリクエスト種別: [登録 | 更新] (2)エラー内容
String warning = String.format(
getString(R.string.warning_register_with_2_reason),
mCurrentPostRequest.getName(), status.getMessage());
showMessageDialog(getString(R.string.error_response_dialog_title),
warning,"WarningDialogFragment");
} else {
// 例外メッセージをダイアログに表示
Exception exception = ((Result.Error<?>) result).getException();
String message = String.format(
getString(R.string.exception_with_reason), exception.getLocalizedMessage());
showMessageDialog(getString(R.string.error_response_dialog_title),message,
"ExceptionDialogFragment");
}
});
}
@Override
public void onCancel() {
// キャンセルボタン押下ならステータスに表示
String cancelMessage = String.format(getString(
R.string.msg_request_canceled_with_type), mCurrentPostRequest.getName());
showStatus(cancelMessage);
}
};
// 送信確認ダイアログ表示
showConfimOkCancelDialog(listener);
}
import json
from typing import Dict, Optional, Union
import sqlalchemy
from flask import Response, abort, g, jsonify, make_response, request
from werkzeug.exceptions import (BadRequest, Conflict, Forbidden,
HTTPException, InternalServerError, NotFound)
#...Daoクラス, Database関連のインポート省略...
MSG_DESCRIPTION: str = "error_message"
ABORT_DICT_BLANK_MESSAGE: Dict[str, str] = {MSG_DESCRIPTION: ""}
# アプリケーションルートパス
APP_ROOT: str = app.config["APPLICATION_ROOT"]
#...データベース処理は省略...
@app.route(APP_ROOT + "/getcurrentdata", methods=["GET"])
def getcurrentdata():
"""メールアドレスと測定日付から健康管理データを取得するリクエスト
:param: request parameter: ?emailAddress=user1@examples.com&measurementDay=2023-0-03
:return: JSON形式(健康管理DBから取得したデータ + 気象センサーDBから取得した天候)
"""
if app_logger_debug:
app_logger.debug(request.path)
app_logger.debug(request.args.to_dict())
# 1.リクエストパラメータチェック
# 1-1.メールアドレス
emailAddress = request.args.get("emailAddress")
if emailAddress is None:
abort(BadRequest.code, _set_errormessage("462,Required EmailAddress."))
# 1-2.測定日付
measurementDay = request.args.get("measurementDay")
if emailAddress is None:
abort(BadRequest.code, _set_errormessage("463,Required MeasurementDay."))
# PersonテーブルにemailAddressが存在するか
personId = _get_personid(emailAddress)
if personId is None:
abort(BadRequest.code, _set_errormessage("461,User is not found."))
# 健康管理DBと気象センサーDBからデータ取得する
#...取得処理は省略...
return make_getdata_success(healthcare_dict)
else:
abort(NotFound.code, _set_errormessage("Data is not found."))
@app.route(APP_ROOT + "/register", methods=["POST"])
def register():
"""
健康管理データ(必須)と天候データ(必須)の登録
"""
if app_logger_debug:
app_logger.debug(request.path)
# 登録データチェック
personId, emailAddress, measurementDay, data = _check_postdata(request)
# 健康管理データ登録 (必須)
_insert_healthdata(personId, measurementDay, data)
# 気象データ登録 (必須) ※気象センサーデータベース
_insert_weather(measurementDay, data)
# ここまでエラーがなければOKレスポンス
return make_register_success(emailAddress, measurementDay)
@app.route(APP_ROOT + "/update", methods=["POST"])
def update():
"""
健康管理データ(任意)または天候データ(任意)の更新
"""
if app_logger_debug:
app_logger.debug(request.path)
# 更新データチェック
personId, emailAddress, measurementDay, data = _check_postdata(request)
# 健康管理データの更新
_update_healthdata(personId, measurementDay, data)
# 天候状態(気象データベース)の更新
_update_weather(measurementDay, data)
# ここまでエラーがなければOKレスポンス
return make_register_success(emailAddress, measurementDay)
def _check_postdata(request):
# リクエストヘッダーチェック
if "application/json" not in request.headers["Content-Type"]:
abort(BadRequest.code, _set_errormessage("450,Bad request Content-Type."))
# 登録用データ取得
data: dict = json.loads(request.data)
if app_logger_debug:
app_logger.debug(data)
# メールアドレスチェック
emailAddress = data.get("emailAddress", None)
if emailAddress is None:
abort(BadRequest.code, _set_errormessage("462,Required EmailAddress."))
# 測定日付チェック
measurementDay = data.get("measurementDay", None)
if measurementDay is None:
abort(BadRequest.code, _set_errormessage("463,Required MeasurementDay."))
# PersonテーブルにemailAddressが存在するか
personId = _get_personid(emailAddress)
if personId is None:
abort(BadRequest.code, _set_errormessage("461,User is not found."))
return personId, emailAddress, measurementDay, data
def make_getdata_success(json_dict: Dict) -> Response:
"""
データ取得処理OKレスポンス
:param json_dict: JSON出力用辞書
:return: Response
"""
resp_obj: Dict = {
"status": {
"code": 0, "message": "OK"},
"data": json_dict
}
return _make_respose(resp_obj, 200)
def make_register_success(email_address: str, measurement_day: str) -> Response:
"""
登録処理OKレスポンス
:param email_address: メールアドレス
:param measurement_day 測定日付
:return: Response
"""
resp_obj: Dict = {
"status":
{"code": 0, "message": "OK"},
"data": {
"emailAddress": email_address,
"measurementDay": measurement_day
}
}
return _make_respose(resp_obj, 200)
def _set_errormessage(message: str) -> Dict:
ABORT_DICT_BLANK_MESSAGE[MSG_DESCRIPTION] = message
return ABORT_DICT_BLANK_MESSAGE
def _make_respose(resp_obj: Dict, resp_code) -> Response:
response = make_response(jsonify(resp_obj), resp_code)
response.headers["Content-Type"] = "application/json"
return response
@app.errorhandler(BadRequest.code)
@app.errorhandler(Forbidden.code)
@app.errorhandler(NotFound.code)
@app.errorhandler(Conflict.code) # IntegrityError (登録済み)
@app.errorhandler(InternalServerError.code)
def error_handler(error: HTTPException) -> Response:
app_logger.warning(f"error_type:{type(error)}, {error}")
resp_obj: Dict[str, Dict[str, Union[int, str]]] = {
"status": {"code": error.code, "message": error.description["error_message"]}
}
return _make_respose(resp_obj, error.code)