スマホアプリ(健康管理)を作ってみた

スマホデビューして1年半、その便利さが身に付くととともに、自分でもツールのようなものを作ってみたくなり、毎日計っている体重計と血圧計の値をグーグルスプレッドシートに入力するアプリを作りました。

ソフト開発はandroid studio 4.0 上で行い、java言語でプログラムを作りました。ソースコードを公開しています。

アプリの概要

スマホアプリの目的は、日々計っている血圧や体重をエクセルなどのスプレッドに保存し健康管理データとして有効利用することです。

これまでは、メモに書いておいて、まとめてエクセルシートに入力していたのを、計測したその場でグーグルシートに送信して保存するので手間も少なく直ぐにチャートなどで見ることが可能となります。

システム構成

システムはGoogle Spread Sheets(スプレッドシート)とGAS( Google Apps Script ) とスマホのプログラムから成っています。

スプレッドシートとGASは、G-Drive(グーグルの提供するWEBサーバーで、データ保存用に15GBまで無料で使える)にユーザー登録することで利用可能となります。

スマホから入力したデータは、GASのプログラムを介して、スプレッドシートに書き込まれ保存されます。

スマホでデータを見たい時には、スプレッドシートから読出し、リストやチャートに表示します。

全てのデータがスプレッドシートに保存されているため、後で、パソコンにダウンロードして、データを再利用することも出来ます。

G-Drive側の作業

スプレッドシートの作成

ブラウザでG-Driveを開き、G-Driveロゴの下の[+新規]をクリック、更に[Googleスプレッドシート]を選択して、新規にスプレッドシートを作成します。

作成したシートには、血圧記録用として下記の項目を入力します。

次に[+]をクリックして新しいシートを作り、体重記録用に下記項目を作ります。

スプレッドシートは共有をクリックして、「このリンクを知っているインターネット上の全員が閲覧できます」を選び、リンク先をコピーしておきます。(次のスクリプトで設定する)

GASスクリプトの作成

次に同様に[+新規]-[その他]-[Google Apps Script]を選択して、下記のスクリプトを同じくG-Driveに新規作成します。

新規作成したスクリプトシートに下記のスクリプトをコピー&ペーストします。

/*** Android Access Scripts ***/

var ss = SpreadsheetApp.openById('作成したシートのリンク先');
//[doPost]
function doPost(e){
var action = e.parameter.action;
if(action == 'putDataBP'){
return putDataBP(e);
}else if(action == 'putDataWt'){
return putDataWt(e);
}
}
//[doGet]
function doGet(e){
// console.log(e.parameter.action, e.parameter.sheet);
var action = e.parameter.action;
// var sheet = ss.getSheetByName(e.parameter.sheet);
if(action == 'getDataBP'){
return getDataBP(e);
}else if(action == 'getDataWt'){
return getDataWt(e);
}
}
//[getDataBP]
// *** return : J arrasy ***
function getDataBP(){
var sheet = ss.getSheetByName("BldPrssr");
var row_st = 2;
var i=0;
var no_rows;
for(i=0; i<10000; i++){
var date_str = sheet.getRange(row_st+i ,1).getValue();
if(date_str == ""){
no_rows = i;
break;
}
}
var rows = [];
rows = sheet.getRange(row_st, 1, no_rows, 5).getDisplayValues();
var result=JSON.stringify(rows);
// return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON);
return ContentService.createTextOutput(result);
}
//[getDataWt]
// *** return : J arrasy ***
function getDataWt(){
var sheet = ss.getSheetByName("Weight");
var row_st = 2;
var i=0;
var no_rows;
for(i=0; i<10000; i++){
var date_str = sheet.getRange(row_st+i ,1).getValue();
if(date_str == ""){
no_rows = i;
break;
}
}
var rows = [];
rows = sheet.getRange(row_st, 1, no_rows, 4).getDisplayValues();
var result=JSON.stringify(rows);
// return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON);
return ContentService.createTextOutput(result);
}
//[putDataBP]**
// param systolicBP,diastolicBP, pls, comment
function putDataBP(e){
/* for release / var systolicBP = e.parameter.systolicBP; var diastolicBP = e.parameter.diastolicBP; var pls = e.parameter.pls; var comment = e.parameter.comment; //
/* for test* var systolicBP = 112; var diastolicBP = 71; var pls = 61; var comment = "this is a comment"; /*/
var sheet = ss.getSheetByName("BldPrssr");
var row_st = 2;
var date_str = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
var time_str = Utilities.formatDate(new Date(), "Asia/Tokyo", "HH:mm");
for(var i=0; i<10000; i++){
// console.log("date_str" + date_str + " sheet " + date_cell);
/if(sheet.getRange(row_st+i, 1).getValue() == date_str){ // 既に本日のデータが存在している(2回目以降は、上書き・・・訂正と同じ) sheet.getRange(row_st+i, 2).setValue(time_str); sheet.getRange(row_st+i, 3).setValue(systolicBP); sheet.getRange(row_st+i, 4).setValue(diastolicBP); sheet.getRange(row_st+i, 5).setValue(pls); sheet.getRange(row_st+i, 6).setValue(comment); break; }else/
if(sheet.getRange(row_st+i, 1).getValue()==""){ // 空白の最下行を探し、本日最初のデータを登録する
sheet.getRange(row_st+i, 1, 1, 2).setNumberFormat('@'); // 先頭列の書式を文字とする
sheet.getRange(row_st+i, 1).setValue(date_str); // 先頭列に日付を入れる
console.log("date_str" + date_str + " time " + time_str);
sheet.getRange(row_st+i, 2).setValue(time_str);
sheet.getRange(row_st+i, 3, 1, 3).setNumberFormat('0'); // カンマ付ではチャート作成でエラーとなる。(#,##0.0から変更)
sheet.getRange(row_st+i, 3).setValue(systolicBP);
sheet.getRange(row_st+i, 4).setValue(diastolicBP);
sheet.getRange(row_st+i, 5).setValue(pls);
sheet.getRange(row_st+i, 6).setValue(comment);
break;
}
}
return ContentService.createTextOutput("Success").setMimeType(ContentService.MimeType.TEXT);
}
//[putDataWt]**
// param weight, bodyfat, comment
function putDataWt(e){
/* for release / var weight = e.parameter.weight; var bodyfat = e.parameter.bodyfat; var comment = e.parameter.comment; //
/* for test* var weight = 55; var bodyfat = 13.5; var comment = "this is a comment"; /*/
var sheet = ss.getSheetByName("Weight");
var row_st = 2;
var date_str = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
var time_str = Utilities.formatDate(new Date(), "Asia/Tokyo", "HH:mm");
for(i=0; i<10000; i++){
/*if(sheet.getRange(row_st+i, 1).getValue()== date_str ){
既に本日のデータが存在している(2回目以降は、上書き・・・訂正と同じ)
sheet.getRange(row_st+i, 2).setValue(time_str);
sheet.getRange(row_st+i, 3).setValue(weight);
sheet.getRange(row_st+i, 4).setValue(bodyfat);
sheet.getRange(row_st+i, 5).setValue(comment);
}else*/ if(sheet.getRange(row_st+i, 1).getValue()==""){ // 空白の最下行を探し、本日最初のデータを登録する sheet.getRange(row_st+i, 1, 1, 2).setNumberFormat('@'); // 先頭列の書式を文字とする sheet.getRange(row_st+i, 1).setValue(date_str); // 先頭列に日付を入れる sheet.getRange(row_st+i, 2).setValue(time_str); sheet.getRange(row_st+i, 3, 1, 2).setNumberFormat('0.0'); // カンマ付ではチャート作成でエラーとなる。(#,##0.0から変更) sheet.getRange(row_st+i, 3).setValue(weight); sheet.getRange(row_st+i, 4).setValue(bodyfat); sheet.getRange(row_st+i, 5).setValue(comment); break; }
}
return ContentService.createTextOutput("Success").setMimeType(ContentService.MimeType.TEXT);
}

スクリプトの2行目”var ss = SpreadsheetApp.openById(‘作成したシートのリンク先’);”に先ほどコピーしたシートのリンク先を入れます。

また、メニューから「公開」をクリック「ウェブアプリケーションとして導入」を選ぶと、左のダイアログが表示されるので、”Who has access to the app”に”Anyone, even nonymous”を選んで「更新」を押します。

すると、このようなダイアログが表示されますので、赤で囲んだURLをコピーして置き、「OK」を押します。

コピーしたURLは下記のような内容になっていますので、これをスマホアプリの初期設定画面で登録します。(/ABCD******WXYZ/ の部分は作成毎に異なる)

https://script.google.com/macros/s/ABCD******WXYZ/exec

なお、スプレッドシートもスクリプトシートも変更時に自動保存されるので、特に「保存」の操作は必要ありません。

スマホアプリの開発

開発ツール

今回作ったのは、android用アプリで開発ツールとして、”android studio”を使いました。android studio はグーグルが提供する無料ソフトで下記からダウンロードしてインストールします。

Install Android Studio  |  Android Developers
Set up and install Android Studio on Windows, macOS, or Linux.

このandroid Studioは開発言語としてjavaの他、グーグルがandroid開発用に提供しているkotlinが使えますが、javaの方が一般的であるのでこれを使いました。

javaでのプログラム開発は初めてでしたが、ツールのサポート機能などが優れており、100%理解しない状態でも、エラーに対する対処方法が表示されるのでこれを潰して行きながら開発が進みました。

また、スマホの画面などは全て感覚的に作成でき、おおまかな構想でスタートして、次第に詳細を詰めて行くと言うプログラム開発が進められるのは、大きなメリットでした。

一方で、トラブルや問題点に遭遇した時には、グーグルのサイトをはじめ、多くの解説記事や動画が参考になり、解決の糸口を見つけることが出来ます。

スマホアプリの機能と画面構成

スマホアプリはjavaで開発しましたが、下記の機能があります。

  • 血圧計、体重計で読取った計測値を入力
  • 入力データをグーグルシートへ書込み
  • 計測データをグーグルシートから読出し、リスト表示、チャート表示
  • 血圧、体重などを管理値として設定し、チャートでリミットラインとして表示
  • 背景画像としてスマホ内の保存写真を選択、表示する

起動画面

起動時の画面です。

メニューとして血圧入力、体重入力、そしてそれぞれのリストおよびチャート表示が選べます。

また、背景画像はスマホ内部保存写真から選択出来ます。

血圧・心拍数入力画面

血圧・心拍数の入力画面です。

入力はスピナー(回転式)となっているので操作性がよく、誤入力が少なくなります。

体重・体脂肪率入力画面

体重・体脂肪率入力画面です。

測定した日時・時刻が自動で記録されます。

血圧・心拍数の一覧表示画面

血圧・心拍数の一覧表示画面です。

同じ日のデータは時刻のみの表示としています。

データは、日付の降順に表示され、スクロールすることで過去のデータが参照できます。

血圧・心拍数チャート表示画面

血圧・心拍数のチャート表示画面です。

青のバーグラフは血圧の上下値を示し、黄色の折れ線は心拍数を表わしています。

管理値を設定することにより、リミットラインを表示することが出来ます。

体重・体脂肪率の一覧表示画面

体重・体脂肪率の一覧表示画面です。

同じ日のデータは時刻のみの表示としています。

データは、日付の降順に表示され、スクロールすることで過去のデータが参照できます。

体重・体脂肪率のチャート表示画面

体重・体脂肪率のチャート表示画面です。

青のバーグラフが体重を表わし、黄色の折れ線が体脂肪率を表わしています。

赤いラインは、リミットラインを示しており、管理値を設定することで表示されます。

android アプリのソフトウェア

以下に、アンドロイド側で作成したソフトのソースを紹介します。

xmlファイル

初期画面 (activity_mai.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80CBC4"
tools:context=".MainActivity"
>

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:minHeight="?actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@android:color/white"
android:background="?attr/colorPrimary"
>
</androidx.appcompat.widget.Toolbar >

<!--TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="食事管理アプリ"
android:textColor="#f0f"
android:textStyle="bold"
android:textSize="30dp"
android:layout_marginTop="12dp"
/-->
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_centerHorizontal="true"
android:layout_marginStart="0dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:foregroundGravity="top"
android:scaleType="centerCrop"
app:srcCompat="@drawable/halto2"
/>


<GridLayout
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_centerHorizontal="true"
android:layout_marginStart="20dp"
android:layout_marginTop="300dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="10dp"
android:columnCount="2"
android:rowCount="2"
>


<androidx.cardview.widget.CardView
android:id="@+id/cv_bldprssr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="0"
android:layout_rowWeight="1"
android:layout_column="0"
android:layout_columnWeight="1"
android:layout_gravity="fill"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="8dp"
>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:gravity="center"
android:orientation="vertical"
>

<ImageView
android:layout_width="140dp"
android:layout_height="100dp"
android:src="@drawable/bldprssr"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="血圧入力"
android:textAlignment="center"
android:textStyle="bold"
/>

</LinearLayout>
</androidx.cardview.widget.CardView>

<androidx.cardview.widget.CardView
android:id="@+id/cv_weight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="0"
android:layout_rowWeight="1"
android:layout_column="1"
android:layout_columnWeight="1"
android:layout_gravity="fill"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="8dp"
>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:gravity="center"
android:orientation="vertical"
>

<ImageView
android:layout_width="140dp"
android:layout_height="100dp"
android:src="@drawable/weight"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="体重入力"
android:textAlignment="center"
android:textStyle="bold"
/>

</LinearLayout>
</androidx.cardview.widget.CardView>

<androidx.cardview.widget.CardView
android:id="@+id/cv_chartBP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="1"
android:layout_rowWeight="1"
android:layout_column="0"
android:layout_columnWeight="1"
android:layout_gravity="fill"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="8dp"
>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:gravity="center"
android:orientation="vertical"
>

<ImageView
android:layout_width="140dp"
android:layout_height="100dp"
android:src="@drawable/chart_bp"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="チャート血圧"
android:textAlignment="center"
android:textStyle="bold"
/>

</LinearLayout>
</androidx.cardview.widget.CardView>

<androidx.cardview.widget.CardView
android:id="@+id/cv_chartWt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="1"
android:layout_rowWeight="1"
android:layout_column="1"
android:layout_columnWeight="1"
android:layout_gravity="fill"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="8dp"
>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:gravity="center"
android:orientation="vertical"
>

<ImageView
android:layout_width="140dp"
android:layout_height="100dp"
android:src="@drawable/chart_wt"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="チャート体重"
android:textAlignment="center"
android:textStyle="bold"
/>

</LinearLayout>
</androidx.cardview.widget.CardView>

</GridLayout>


</RelativeLayout>

血圧入力画面 (input_bldprssr.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DCEDC8"
>

<TextView
android:id="@+id/tv_bladpress0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:text="血圧"
android:textSize="20dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

<TextView
android:id="@+id/tv_pls0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="24dp"
android:text="心拍数"
android:textSize="20dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/np_diastolicBP"
/>

<NumberPicker
android:id="@+id/np_diastolicBP"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#fff"
android:ems="10"
android:solidColor="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="@+id/np_systolicBP"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/np_systolicBP"
/>

<NumberPicker
android:id="@+id/np_systolicBP"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="8dp"
android:background="#fff"
android:ems="10"
android:solidColor="@color/colorAccent"
app:layout_constraintEnd_toStartOf="@+id/np_diastolicBP"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_bladpress0"
/>

<NumberPicker
android:id="@+id/np_pls"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="8dp"
android:background="#fff"
android:ems="10"
android:solidColor="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_pls0"
/>

<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="送信"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btn_cancel"
app:layout_constraintTop_toBottomOf="@+id/np_pls"
/>

<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="キャンセル"
app:layout_constraintEnd_toStartOf="@+id/btn_send"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/np_pls"
/>

</androidx.constraintlayout.widget.ConstraintLayout>

体重・体脂肪率入力画面 (input_weight.xml) 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DCEDC8"
><![CDATA[>

]]>

<TextView
android:id="@+id/tv_weight0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:text="体重(kg)"
android:textSize="20dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

<TextView
android:id="@+id/tv_bodyFat0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:text="体脂肪率(%)"
android:textSize="20dp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/np_weight"
/>

<NumberPicker
android:id="@+id/np_weight"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_marginTop="8dp"
android:background="#fff"

android:ems="10"
android:inputType="textPersonName"
android:solidColor="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_weight0"
/>

<NumberPicker
android:id="@+id/np_weightFrct"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:background="#fff"
android:ems="10"


android:solidColor="@color/colorAccent"
app:layout_constraintStart_toEndOf="@id/np_weight"
app:layout_constraintTop_toTopOf="@+id/np_weight"
/>

<NumberPicker
android:id="@+id/np_bodyFat"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_marginTop="8dp"
android:background="#fff"
android:ems="10"
android:solidColor="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_bodyFat0"
/>

<NumberPicker
android:id="@+id/np_bodyFatFrct"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:background="#fff"
android:ems="10"
android:solidColor="@color/colorAccent"
app:layout_constraintStart_toEndOf="@id/np_bodyFat"
app:layout_constraintTop_toTopOf="@+id/np_bodyFat"
/>

<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="キャンセル"
app:layout_constraintEnd_toStartOf="@+id/btn_send"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/np_bodyFat"
/>

<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="送信"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btn_cancel"
app:layout_constraintTop_toBottomOf="@+id/np_bodyFat"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

チャート表示アクティビティ (activity_chart.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>

<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
>
<!--androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:minHeight="?actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@android:color/white"
android:background="?attr/colorPrimary">
</androidx.appcompat.widget.Toolbar -->
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="?actionBarSize"
android:padding="@dimen/appbar_padding"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
/>

<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00838F"
/>
</com.google.android.material.appbar.AppBarLayout>

<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:backgroundTint="@color/colorFAB"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_home_24"
/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

データ一覧表示画面 (fragment_item.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:name="com.example.HealthManagement.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ItemFragment"
tools:listitem="@layout/fragment_item"
/>

データ一覧表示画面 (fragment_item.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:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="#80CBC4"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
>

<TextView
android:id="@+id/tv_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="2dp"
android:background="#FDD835"
android:text="2020/02/02"
android:layout_gravity="center_horizontal"
android:textAppearance="?attr/textAppearanceListItem"
android:textSize="20dp"
/>
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:text="12:12"
android:background="#FDD835"
android:textSize="20dp"
android:textAppearance="?attr/textAppearanceListItem"
/>

</androidx.appcompat.widget.LinearLayoutCompat>

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>

<TextView
android:id="@+id/tv_value1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"
/>

<TextView
android:id="@+id/tv_value2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"
/>

<TextView
android:id="@+id/tv_value3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"
/>
</androidx.appcompat.widget.LinearLayoutCompat>

</LinearLayout>

チャート表示画面 (fragment_combined_chart

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>

<com.github.mikephil.charting.charts.CombinedChart
android:id="@+id/conbined_chart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
/>

</LinearLayout>

設定画面 (activity_settings.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>


<TextView
android:id="@+id/tV_disc1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:background="@color/colorPrimary"
android:text="GoogleSpreadSheetsアクセスURL設定"
android:textColor="@android:color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

<EditText
android:id="@+id/et_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="new url"
android:inputType="textUri"
app:layout_constraintStart_toStartOf="@+id/tv_url"
app:layout_constraintTop_toBottomOf="@+id/tv_url"
/>

<TextView
android:id="@+id/tv_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="TextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tV_disc1"
/>


<TextView
android:id="@+id/tV_disc2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:background="@color/colorPrimary"
android:text="各項目の上下限値設定"
android:textColor="@android:color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_url"
/>


<TextView
android:id="@+id/tv_ul0"
android:layout_width="38dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:text="上限"
app:layout_constraintStart_toEndOf="@+id/tv_ul_BP0"
app:layout_constraintTop_toBottomOf="@+id/tV_disc2"
/>

<TextView
android:id="@+id/tv_ll0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:text="下限"
app:layout_constraintStart_toEndOf="@+id/tv_ul0"
app:layout_constraintTop_toBottomOf="@+id/tV_disc2"
/>


<TextView
android:id="@+id/tv_ul_BP0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:text="血圧(mmHg)"
app:layout_constraintBaseline_toBaselineOf="@+id/et_ulBP"
app:layout_constraintStart_toStartOf="parent"
/>

<EditText
android:id="@+id/et_ulBP"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="number"
android:text="120"
app:layout_constraintStart_toStartOf="@+id/tv_ul0"
app:layout_constraintTop_toBottomOf="@+id/tv_ul0"
/>

<EditText
android:id="@+id/et_llBP"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="number"
android:text="85"
app:layout_constraintStart_toStartOf="@+id/tv_ll0"
app:layout_constraintTop_toBottomOf="@+id/tv_ul0"
/>

<TextView
android:id="@+id/tv_pls0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="心拍数(bpm)"
app:layout_constraintBaseline_toBaselineOf="@+id/et_ulPls"
app:layout_constraintStart_toStartOf="@+id/tv_ul_BP0"
/>

<EditText
android:id="@+id/et_ulPls"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="number"
android:text="75"
app:layout_constraintStart_toStartOf="@+id/et_ulBP"
app:layout_constraintTop_toBottomOf="@+id/et_ulBP"
/>

<EditText
android:id="@+id/et_llPls"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="number"
android:text="65"
app:layout_constraintStart_toStartOf="@+id/et_llBP"
app:layout_constraintTop_toBottomOf="@+id/et_llBP"
/>

<TextView
android:id="@+id/tv_weight0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="体重(kg)"
app:layout_constraintBaseline_toBaselineOf="@+id/et_ulWt"
app:layout_constraintStart_toStartOf="@+id/tv_pls0"
/>

<EditText
android:id="@+id/et_ulWt"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="numberDecimal"
android:text="50"
app:layout_constraintStart_toStartOf="@+id/et_ulPls"
app:layout_constraintTop_toBottomOf="@+id/et_ulPls"
/>

<EditText
android:id="@+id/et_llWt"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="numberDecimal"
android:text="50"
app:layout_constraintStart_toStartOf="@+id/et_llPls"
app:layout_constraintTop_toBottomOf="@+id/et_llPls"
/>

<TextView
android:id="@+id/tv_bodyFat0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="15.0"
android:inputType="numberDecimal"
android:text="体脂肪率(%)"
app:layout_constraintBaseline_toBaselineOf="@+id/et_ulbF"
app:layout_constraintStart_toStartOf="@+id/tv_weight0"
/>

<EditText
android:id="@+id/et_ulbF"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="numberDecimal"
android:text="15.5"
app:layout_constraintStart_toStartOf="@+id/et_ulWt"
app:layout_constraintTop_toBottomOf="@+id/et_ulWt"
/>

<EditText
android:id="@+id/et_llbF"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="numberDecimal"
android:text="10.5"
app:layout_constraintStart_toStartOf="@+id/et_llWt"
app:layout_constraintTop_toBottomOf="@+id/et_llWt"
/>

<Button
android:id="@+id/bt_setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="設定"
app:layout_constraintBottom_toBottomOf="@+id/bt_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bt_cancel"
app:layout_constraintTop_toTopOf="@+id/bt_cancel"
/>

<Button
android:id="@+id/bt_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="キャンセル"
app:layout_constraintEnd_toStartOf="@+id/bt_setting"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_ulbF"
/>

</androidx.constraintlayout.widget.ConstraintLayout>

背景画像選択画面 (activity_image.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
tools:context="com.example.HealthManagement.ImageActivity"
xmlns:android="http://schemas.android.com/apk/res/android"
>

<!-- com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</com.google.android.material.appbar.AppBarLayout-->

<include layout="@layout/content_main" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_menu_gallery"
app:backgroundTint="@color/colorFAB"
/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

背景画像表示 (model.xml)

<?xml version="1.0" encoding="utf-8"?>

<androidx.cardview.widget.CardView android:orientation="horizontal"
android:layout_width="match_parent"
app:cardCornerRadius="5dp"
android:layout_height="200dp"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:textandroid="http://schemas.android.com/tools"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>

<ImageView
android:id="@+id/spacecraftImg"
android:src="@drawable/placeholder"
android:layout_width="150dp"
android:layout_height="wrap_content"
/>

<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/nameTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Name"
android:padding="10dp"
android:textColor="@color/colorAccent"
android:textStyle="bold"
android:layout_alignParentLeft="true"
/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

javaプログラム

メインプログラム (MainActivity.java)

<?xml version="1.0" encoding="utf-8"?>

<androidx.cardview.widget.CardView android:orientation="horizontal"
android:layout_width="match_parent"
app:cardCornerRadius="5dp"
android:layout_height="200dp"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:textandroid="http://schemas.android.com/tools"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>

<ImageView
android:id="@+id/spacecraftImg"
android:src="@drawable/placeholder"
android:layout_width="150dp"
android:layout_height="wrap_content"
/>

<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/nameTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Name"
android:padding="10dp"
android:textColor="@color/colorAccent"
android:textStyle="bold"
android:layout_alignParentLeft="true"
/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

血圧・心拍数入力 (InputBldPrssr.java)

package com.example.HealthManagement;

//import android.app.ProgressDialog;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.NumberPicker;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;

import static com.example.HealthManagement.ChartActivity.ChartDataReady_BP;

public class InputBldPrssr extends AppCompatActivity implements View.OnClickListener {


SharedPreferences dataSave;
NumberPicker np_systolicBP, np_diastolicBP, np_pls;
Button buttonSend, buttonCansel;
DelayedProgressDialog progressDialog;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.input_bldprssr);

np_systolicBP = (NumberPicker) findViewById(R.id.np_systolicBP);
np_diastolicBP = (NumberPicker) findViewById(R.id.np_diastolicBP);
np_pls = (NumberPicker) findViewById(R.id.np_pls);

np_systolicBP.setMaxValue(150);
np_systolicBP.setMinValue(80);

np_diastolicBP.setMaxValue(90);
np_diastolicBP.setMinValue(50);

np_pls.setMaxValue(100);
np_pls.setMinValue(50);


buttonSend = (Button)findViewById(R.id.btn_send);
buttonSend.setOnClickListener(this);
buttonCansel = (Button)findViewById(R.id.btn_cancel);
buttonCansel.setOnClickListener(this);

dataSave = getSharedPreferences("DataStore", MODE_PRIVATE);
String pr_sBP = dataSave.getString("systolicBP", null);
String pr_dBP = dataSave.getString("diastolicBP", null);
String pr_pls = dataSave.getString("pls", null);
if(pr_sBP != null ) {
np_systolicBP.setValue(Integer.parseInt(pr_sBP));
np_diastolicBP.setValue(Integer.parseInt(pr_dBP));
np_pls.setValue(Integer.parseInt(pr_pls));
}else{
np_systolicBP.setValue(100);
np_diastolicBP.setValue(70);
np_pls.setValue(70);
}

setTitle("血圧・心拍数入力");

}

//This is the part where data is transafeered from Your Android phone to Sheet by using HTTP Rest API calls
private void addItemToSheet() {
Log.d("myTag","addItemToSheet");

final String systolicBP = String.valueOf(np_systolicBP.getValue());
final String diastolicBP = String.valueOf(np_diastolicBP.getValue());
final String pls = String.valueOf(np_pls.getValue());
Log.d("myTag", "parameter " + systolicBP + " " + diastolicBP+ " " +pls);

SharedPreferences.Editor editor = dataSave.edit();
editor.putString("systolicBP", String.valueOf(np_systolicBP.getValue()));
editor.putString("diastolicBP", String.valueOf(np_diastolicBP.getValue()));
editor.putString("pls", String.valueOf(np_pls.getValue()));
editor.commit();

String url = dataSave.getString("url", null);
if( url == null){
Toast.makeText(this,"GoogleSheetへのアクセスurlが設定されていません。[初期設定]画面で設定して下さい。",Toast.LENGTH_LONG).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}

progressDialog = new DelayedProgressDialog();
progressDialog.show(getSupportFragmentManager(), "Accessing to Google Sheets");


url += "?";
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {

//loading.dismiss();
progressDialog.cancel();
Toast.makeText(InputBldPrssr.this,response,Toast.LENGTH_LONG).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {

}
}
) {
@Override
protected Map<String, String> getParams() {
Map<String, String> parmas = new HashMap<>();
//here we pass params
parmas.put("action","putDataBP");
parmas.put("systolicBP",systolicBP);
parmas.put("diastolicBP",diastolicBP);
parmas.put("pls",pls);
Log.d("myTag", "parameter" + parmas);

return parmas;
}
};

int socketTimeOut = 10000;// u can change this .. here it is 50 seconds

RetryPolicy retryPolicy = new DefaultRetryPolicy(socketTimeOut, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
stringRequest.setRetryPolicy(retryPolicy);

RequestQueue queue = Volley.newRequestQueue(this);

queue.add(stringRequest);

}

@Override
public void onClick(View v) {
Log.d("myTag","onClick");
if(v==buttonSend){
addItemToSheet();
ChartDataReady_BP = false;
}else if(v==buttonCansel){
finish();
}

}
/* @Override
public void onDestroy(){
super.onDestroy();
// Log.v("LifeCycle", "onDestroy");
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
et_Intake.clearFocus();
}*/
}

体重・体脂肪率入力 (InputWeight.java)

package com.example.HealthManagement;

// import android.app.ProgressDialog;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.NumberPicker;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import static com.example.HealthManagement.ChartActivity.ChartDataReady_Wt;

public class InputWeight extends AppCompatActivity implements View.OnClickListener {

SharedPreferences dataSave;
NumberPicker np_weight, np_bodyfat, np_weightFrct, np_bodyfatFrct;
Button buttonSend, buttonCancel;
DelayedProgressDialog progressDialog;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.input_weight);

String nums[]= {".0", ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"};

np_weight = (NumberPicker) findViewById(R.id.np_weight);
np_weightFrct = (NumberPicker) findViewById(R.id.np_weightFrct);
np_bodyfat = (NumberPicker) findViewById(R.id.np_bodyFat);
np_bodyfatFrct = (NumberPicker) findViewById(R.id.np_bodyFatFrct);
// Log.d("myTag","input np_bodyFatFrct");

np_weight.setMaxValue(70);
np_weight.setMinValue(40);


np_weightFrct.setMaxValue(nums.length-1);
np_weightFrct.setMinValue(0);
np_weightFrct.setWrapSelectorWheel(true);
np_weightFrct.setDisplayedValues(nums);
np_weightFrct.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);

np_bodyfat.setMaxValue(30);
np_bodyfat.setMinValue(5);


np_bodyfatFrct.setMaxValue(nums.length-1);
np_bodyfatFrct.setMinValue(0);
np_bodyfatFrct.setWrapSelectorWheel(true);
np_bodyfatFrct.setDisplayedValues(nums);
np_bodyfatFrct.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);


Log.d("myTag","input np_bodyfatFrct");

buttonSend = (Button)findViewById(R.id.btn_send);
buttonSend.setOnClickListener(this);
buttonCancel = (Button)findViewById(R.id.btn_cancel);
buttonCancel.setOnClickListener(this);

dataSave = getSharedPreferences("DataStore", MODE_PRIVATE);
String pr_weight = dataSave.getString("weight", null);
if(pr_weight != null ) {
np_weight.setValue(Integer.parseInt(pr_weight));
}else{
np_weight.setValue(50);
}
String pr_bodyfat = dataSave.getString("bodyfat", null);
if(pr_bodyfat != null ) {
np_bodyfat.setValue(Integer.parseInt(pr_bodyfat));
}else{
np_bodyfat.setValue(15);
}

setTitle("体重・体脂肪率入力");

}

//This is the part where data is transafeered from Your Android phone to Sheet by using HTTP Rest API calls
private void addItemToSheet() {

// final ProgressDialog loading = ProgressDialog.show(this,"Adding Item","Please wait");
/* final String name = editTextItemName.getText().toString().trim();
final String brand = editTextBrand.getText().toString().trim();*/

/* final String sheetName = sp_sheetName.getSelectedItem().toString();
final String meal = sp_meal.getSelectedItem().toString();
final String Intake = et_Intake.getText().toString().trim();
final String foodItemNo = tv_foodItemNo.getText().toString();*/

final String weight = String.valueOf(np_weight.getValue()) + "." + np_weightFrct.getValue();
final String bodyfat = String.valueOf(np_bodyfat.getValue()) + "." + np_bodyfatFrct.getValue();


SharedPreferences.Editor editor = dataSave.edit();
editor.putString("weight", String.valueOf(np_weight.getValue()));
editor.putString("bodyfat", String.valueOf(np_bodyfat.getValue()));
editor.commit();

String url = dataSave.getString("url", null);
if( url == null){
Toast.makeText(this,"GoogleSheetへのアクセスurlが設定されていません。[初期設定]画面で設定して下さい。",Toast.LENGTH_LONG).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}
progressDialog = new DelayedProgressDialog();
progressDialog.show(getSupportFragmentManager(), "Accessing to Google Sheets");

url += "?";
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {

//loading.dismiss();
progressDialog.cancel();
Toast.makeText(InputWeight.this,response,Toast.LENGTH_LONG).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {

}
}
) {
@Override
protected Map<String, String> getParams() {
Map<String, String> parmas = new HashMap<>();

//here we pass params
parmas.put("action","putDataWt");
parmas.put("weight",weight);
parmas.put("bodyfat",bodyfat);

return parmas;
}
};

int socketTimeOut = 10000;// u can change this .. here it is 50 seconds

RetryPolicy retryPolicy = new DefaultRetryPolicy(socketTimeOut, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
stringRequest.setRetryPolicy(retryPolicy);

RequestQueue queue = Volley.newRequestQueue(this);

queue.add(stringRequest);

}

@Override
public void onClick(View v) {

if(v==buttonSend){
addItemToSheet();
ChartDataReady_Wt=false;
}else if(v==buttonCancel){
finish();
}

}
/* @Override
public void onDestroy(){
super.onDestroy();
// Log.v("LifeCycle", "onDestroy");
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
et_Intake.clearFocus();
}*/
}

一覧表示画面・チャート表示 (ChartActivity.java)

package com.example.HealthManagement;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.ViewPager;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.example.HealthManagement.dummy.DummyContent;
import com.example.HealthManagement.main.SectionsPagerAdapter;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.CandleEntry;
import com.github.mikephil.charting.data.Entry;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;

import org.json.JSONArray;
import org.json.JSONException;

import static com.example.HealthManagement.ConbinedChartFragment.EntriesBar;
import static com.example.HealthManagement.ConbinedChartFragment.EntriesCDL;
import static com.example.HealthManagement.ConbinedChartFragment.EntriesLineBP;
import static com.example.HealthManagement.ConbinedChartFragment.EntriesLineWt;
import static com.example.HealthManagement.ConbinedChartFragment.XAxisLabelBP;
import static com.example.HealthManagement.ConbinedChartFragment.XAxisLabelWt;
import static com.example.HealthManagement.dummy.DummyContent.ITEMS_BP;
import static com.example.HealthManagement.dummy.DummyContent.ITEMS_Wt;


public class ChartActivity extends AppCompatActivity {

// static ArrayList<BarEntry> entries = new ArrayList<BarEntry>();
public static boolean ChartDataReady_BP=false;
public static boolean ChartDataReady_Wt=false;
public static String act;

DelayedProgressDialog progressDialog;
final Handler handler = new Handler();

TextView tv_title;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chart);


/* Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

// Backボタンを有効にする
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);*/
tv_title = findViewById(R.id.tv_title);

Intent intent = getIntent();
act = intent.getStringExtra("action");
if(act.equals("getDataBP")){
tv_title.setText("血圧・脈拍数");
}else if(act.equals("getDataWt")) {
tv_title.setText("体重・体脂肪率");
}
assert act != null; // ?分からないけどサジェスチョンにより入れて置く
if((act.equals("getDataBP")&&!ChartDataReady_BP) || (act.equals("getDataWt")&&!ChartDataReady_Wt)){
getDataForChart();
}else{
// wait、sleep、delayがうまく動作しないので、ここに持ってくる
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(sectionsPagerAdapter);

TabLayout tabs = findViewById(R.id.tabs);
tabs.setupWithViewPager(viewPager);
FloatingActionButton fab = findViewById(R.id.fab);

fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// .setAction("Action", null).show();

Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}
});
}
}

private <item_list> void getDataForChart() {



SharedPreferences dataSave = getSharedPreferences("DataStore", MODE_PRIVATE);

String url = dataSave.getString("url", null);
// Log.d("myTag","url = " + url);
if( url == null){
Toast.makeText(this,"GoogleSheetへのアクセスurlが設定されていません。[初期設定]画面で設定して下さい。",Toast.LENGTH_LONG).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}


progressDialog = new DelayedProgressDialog();
progressDialog.show(getSupportFragmentManager(), "Accessing to Google Sheets");


if(act.equals("getDataBP")){
url += "?action=getDataBP";
// Log.d("myTag","BP act = " + act );
}else if(act.equals("getDataWt")) {
url += "?action=getDataWt";
// Log.d("myTag","WT act = " + act );
}
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {

parseItems(response);

// Log.d("myTag","finish parse Item Items = " + ITEMS );
}
},

new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Log.d("myTag","finish parse ERROR Items = " + ITEMS );
}
}
);


int socketTimeOut = 50000;
RetryPolicy policy = new DefaultRetryPolicy(socketTimeOut, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);

stringRequest.setRetryPolicy(policy);

RequestQueue queue = Volley.newRequestQueue(this);
queue.add(stringRequest);

}


private void parseItems(String jsonResponse) {
try {
JSONArray jarray = new JSONArray(jsonResponse);
if(act.equals("getDataBP")) {
ITEMS_BP.clear();
EntriesLineBP.clear();
EntriesCDL.clear();
}else if(act.equals("getDataWt")) {
ITEMS_Wt.clear();
EntriesLineWt.clear();
EntriesBar.clear();
}

String prev_date = "";
for (int i = 0; i < jarray.length(); i++) {
// Log.d("myTag","i / Number of Data(list) = " + i + ", "+ jarray.length() );
//逆順に並べる
String sarray = jarray.get(jarray.length()-1-i).toString();
JSONArray jarray2 = new JSONArray(sarray);

if(act.equals("getDataBP")) { // 血圧・脈拍
if(prev_date.equals(jarray2.get(0).toString())) {
ITEMS_BP.add(new DummyContent.DummyItem(String.valueOf(i), "", jarray2.get(1).toString(), "血圧: " + jarray2.get(2).toString() +" -" , jarray2.get(3).toString()+" mmHg", "脈拍: " + jarray2.get(4).toString()+" bpm"));
}else{
ITEMS_BP.add(new DummyContent.DummyItem(String.valueOf(i), jarray2.get(0).toString(), jarray2.get(1).toString(), "血圧: " + jarray2.get(2).toString() +" -" , jarray2.get(3).toString()+" mmHg", "脈拍: " + jarray2.get(4).toString()+" bpm"));
}
}else if(act.equals("getDataWt")) { // 体重・体脂肪率
if(prev_date.equals(jarray2.get(0).toString())) {
ITEMS_Wt.add( new DummyContent.DummyItem(String.valueOf(i),"", jarray2.get(1).toString(), "体重: " + jarray2.get(2).toString()+" kg", "体脂肪率: "+ jarray2.get(3).toString()+" %",""));
}else{
ITEMS_Wt.add(new DummyContent.DummyItem(String.valueOf(i),jarray2.get(0).toString(), jarray2.get(1).toString(), "体重: " + jarray2.get(2).toString()+" kg", "体脂肪率: "+ jarray2.get(3).toString()+" %",""));
}
}
prev_date = jarray2.get(0).toString();
}
// チャート用のデータセットを用意する
int j=0;
prev_date = "";
for (int i = 0; i < jarray.length(); i++) {
// Log.d("myTag","i / Number of Data(chart) = " + i + ", " + jarray.length() );
String sarray = jarray.get(i).toString();
JSONArray jarray2 = new JSONArray(sarray);

if(act.equals("getDataBP")) { // 血圧・脈拍
// if((!prev_date.equals(jarray2.get(0).toString())) || (jarray.length() < 20) ) {
EntriesLineBP
.add(new Entry((float) j,Float.parseFloat(jarray2.get(4).toString())));
EntriesCDL.add(new CandleEntry((float) j , Float.parseFloat(jarray2.get(2).toString()), Float.parseFloat(jarray2.get(3).toString()), Float.parseFloat(jarray2.get(2).toString()), Float.parseFloat(jarray2.get(3).toString())));
XAxisLabelBP.add(jarray2.get(0).toString());
j++;
// }


}else if(act.equals("getDataWt")) { // 体重・体脂肪率
if((!prev_date.equals(jarray2.get(0).toString())) || (jarray.length() < 20) ){
EntriesLineWt.add(new Entry((float) j,Float.parseFloat(jarray2.get(3).toString())));
EntriesBar.add(new BarEntry((float) j, Float.parseFloat(jarray2.get(2).toString())));
XAxisLabelWt.add(jarray2.get(0).toString());
// EntriesCDL.add(new CandleEntry((float) j , 0f,0f,0f,0f));
j++;

/* }else{
// 同じ日のデータは後の方を採用 。
entries.get(j-1).setX((float) j-1);
entries.get(j-1).setY(Float.parseFloat(jarray2.get(3).toString()));
entries1.get(j-1).setX((float) j-1);
entries1.get(j-1).setY(Float.parseFloat(jarray2.get(2).toString()));
// XAxisLabel.add(jarray2.get(0).toString().toString());
// entriesCDL.add(new CandleEntry((float) j, 0f, 0f, 0f, 0f));
*/
}


}
prev_date = jarray2.get(0).toString();
}
if(act.equals("getDataBP")){
ChartDataReady_BP = true;
}else{
ChartDataReady_Wt = true;
}
} catch (JSONException e) {

e.printStackTrace();
}
//dismiss or cancel the dialog
progressDialog.cancel();


/************************************************/
// Log.d("myTag", "list = " + list );
// 値の取出しテスト Log.d("myTag", "要素を取出す[3]のdate = " + list.get(3).get("date") );

// wait、sleep、delayがうまく動作しないので、ここに持ってくる
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(sectionsPagerAdapter);

TabLayout tabs = findViewById(R.id.tabs);
tabs.setupWithViewPager(viewPager);
FloatingActionButton fab = findViewById(R.id.fab);

fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// .setAction("Action", null).show();

Intent intent = new Intent(getApplicationContext(),MainActivity.class);
startActivity(intent);
}
});
/****************************************************/
/* editTextSearchItem.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
ListItem.this.adapter.getFilter().filter(charSequence);
}
@Override
public void afterTextChanged(Editable editable) {
}
});
*/
}
}

チャート表示フラグメント (CombinationChartFragment.java)

package com.example.HealthManagement;

import android.app.AlertDialog;
import android.graphics.Color;
import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.CombinedChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.CandleData;
import com.github.mikephil.charting.data.CandleDataSet;
import com.github.mikephil.charting.data.CandleEntry;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.ValueFormatter;

import java.util.ArrayList;
import java.util.Objects;
import java.util.Random;

import static com.example.HealthManagement.MainActivity.Lowerlimit;
import static com.example.HealthManagement.MainActivity.Upperlimit;
import static com.example.HealthManagement.ChartActivity.act;

/**
* A simple {
@link Fragment} subclass.
* Use the {
@link ConbinedChartFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ConbinedChartFragment extends Fragment {

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private
String mParam1;
private String mParam2;

private CombinedChart chart;
YAxis rightAxis, leftAxis;
// private final int count = 12;

public static ArrayList<Entry> EntriesLineBP = new ArrayList<>();
public static ArrayList<Entry> EntriesLineWt = new ArrayList<>();
public static ArrayList<BarEntry> EntriesBar = new ArrayList<>();
// public static ArrayList<BarEntry> entries2 = new ArrayList<>(); // (th) not use of stack
public static ArrayList<CandleEntry> EntriesCDL = new ArrayList<>();
public static ArrayList<String> XAxisLabelBP = new ArrayList<>();
public static ArrayList<String> XAxisLabelWt = new ArrayList<>();
public ConbinedChartFragment() {
// Required empty public constructor
}

/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*
@param param1 Parameter 1.
*
@param param2 Parameter 2.
*
@return A new instance of fragment ConbinedChartFragment.
*/
//
TODO: Rename and change types and number of parameters
public static
ConbinedChartFragment newInstance(String param1, String param2) {
ConbinedChartFragment fragment = new ConbinedChartFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_conbined_chart, container, false);

//setTitle("CombinedChartActivity");
chart = (CombinedChart) view.findViewById(R.id.conbined_chart);

chart.getDescription().setEnabled(false);
chart.setBackgroundColor(Color.LTGRAY);
chart.setDrawGridBackground(false);
chart.setDrawBarShadow(false);
chart.setHighlightFullBarEnabled(false);

// draw bars behind lines
chart.setDrawOrder(new CombinedChart.DrawOrder[]{
CombinedChart.DrawOrder.BAR, CombinedChart.DrawOrder.CANDLE,CombinedChart.DrawOrder.LINE
});

Legend l = chart.getLegend();
l.setWordWrapEnabled(true);
l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM);
l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
l.setOrientation(Legend.LegendOrientation.HORIZONTAL);
l.setDrawInside(false);

rightAxis = chart.getAxisRight();
rightAxis.setDrawGridLines(false);
// rightAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true)

leftAxis = chart.getAxisLeft();
leftAxis.setDrawGridLines(true );
// leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true)


if(act.equals("getDataWt")) {
if( Objects.requireNonNull(Upperlimit.get("weight")).length() != 0){
// Log.d("myTag", "UpperLimit of " + mParam1 + " = " + Upperlimit.get(mParam1));
LimitLine line1 = new LimitLine(Float.parseFloat(Objects.requireNonNull(Upperlimit.get("weight"))), "体重上限"+Upperlimit.get("weight") + "kg");
line1.setLineWidth(4f);
line1.enableDashedLine(10f, 10f, 0f);
line1.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
line1.setTextSize(10f);
leftAxis.addLimitLine(line1);
}
if( Lowerlimit.get("weight").length() != 0){
// Log.d("myTag", "Lowerlimit of " + mParam1 + " = " + Lowerlimit.get(mParam1));
LimitLine line2 = new LimitLine(Float.parseFloat(Objects.requireNonNull(Lowerlimit.get("weight"))), "体重下限"+Lowerlimit.get("weight") + "kg");
line2.setLineWidth(4f);
line2.enableDashedLine(10f, 10f, 0f);
line2.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_BOTTOM);
line2.setTextSize(10f);

leftAxis.addLimitLine(line2);
}
if( Upperlimit.get("bodyfat").length() != 0){
Log.d("myTag", "UpperLimit of bodyfat = " + Upperlimit.get("bodyfat"));
LimitLine line3 = new LimitLine(Float.parseFloat(Objects.requireNonNull(Upperlimit.get("bodyfat"))), "体脂肪上限"+Upperlimit.get("bodyfat") + "%");
line3.setLineWidth(4f);
line3.enableDashedLine(10f, 10f, 0f);
line3.setLabelPosition(LimitLine.LimitLabelPosition.RIGHT_TOP);
line3.setTextSize(10f);
line3.setLineColor(Color.rgb(240, 180, 70));
rightAxis.addLimitLine(line3);
}
if( Lowerlimit.get("bodyfat").length() != 0){
// Log.d("myTag", "Lowerlimit of " + mParam1 + " = " + Lowerlimit.get(mParam1));
LimitLine line4 = new LimitLine(Float.parseFloat(Objects.requireNonNull(Lowerlimit.get("bodyfat"))), "体脂肪下限"+Lowerlimit.get("bodyfat")+ "%");
line4.setLineWidth(4f);
line4.enableDashedLine(10f, 10f, 0f);
line4.setLabelPosition(LimitLine.LimitLabelPosition.RIGHT_BOTTOM);
line4.setTextSize(10f);
line4.setLineColor(Color.rgb(240, 180, 70));
rightAxis.addLimitLine(line4);
}

}else if(act.equals("getDataBP")) {
if( Upperlimit.get("BP").length() != 0){
LimitLine line1 = new LimitLine(Float.parseFloat(Objects.requireNonNull(Upperlimit.get("BP"))), "血圧上限(上)"+Upperlimit.get("BP") + "mmHg");
line1.setLineWidth(4f);
line1.enableDashedLine(10f, 10f, 0f);
line1.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
line1.setTextSize(10f);
leftAxis.addLimitLine(line1);
}
if( Lowerlimit.get("BP").length() != 0){
LimitLine line2 = new LimitLine(Float.parseFloat(Lowerlimit.get("BP")), "血圧上限(下)"+Lowerlimit.get("BP") + "mmHg");
line2.setLineWidth(4f);
line2.enableDashedLine(10f, 10f, 0f);
line2.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
line2.setTextSize(10f);
leftAxis.addLimitLine(line2);
}
if( Upperlimit.get("pulse").length() != 0){
LimitLine line3 = new LimitLine(Float.parseFloat(Upperlimit.get("pulse")), "脈拍上限"+Upperlimit.get("pulse") + "bpm");
line3.setLineWidth(4f);
line3.enableDashedLine(10f, 10f, 0f);
line3.setLabelPosition(LimitLine.LimitLabelPosition.RIGHT_TOP);
line3.setTextSize(10f);
line3.setLineColor(Color.rgb(240, 180, 70));
rightAxis.addLimitLine(line3);
}
if( Lowerlimit.get("pulse").length() != 0){
LimitLine line4 = new LimitLine(Float.parseFloat(Lowerlimit.get("pulse")), "脈拍下限"+Lowerlimit.get("pulse") + "bpm");
line4.setLineWidth(4f);
line4.enableDashedLine(10f, 10f, 0f);
line4.setLabelPosition(LimitLine.LimitLabelPosition.RIGHT_BOTTOM);
line4.setTextSize(10f);
line4.setLineColor(Color.rgb(240, 180, 70));
rightAxis.addLimitLine(line4);
}

}

XAxis xAxis = chart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.TOP);
xAxis.setAxisMinimum(0f);
xAxis.setTextSize(12f);
xAxis.setGranularity(0f);
xAxis.setLabelRotationAngle(-75);
xAxis.setDrawGridLines(false);
xAxis.setGranularityEnabled(true);
xAxis.setCenterAxisLabels(false); // ここがtrueではうまく行かない

xAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
if(act.equals("getDataBP")) {
return XAxisLabelBP.get((int) value);
}else if(act.equals("getDataWt")) {
return XAxisLabelWt.get((int) value);
// return months[(int) value % months.length];
}
return null;
}
});

CombinedData data = new CombinedData();

data.setData(generateLineData());

if(act.equals("getDataWt")) {
data.setData(generateBarData());
}else if(act.equals("getDataBP")) {
data.setData(generateCandleData());
}
xAxis.setAxisMaximum(data.getXMax() + 0.25f);

chart.setData(data);
chart.invalidate();

return view;

}
private LineData generateLineData() {

LineData d = new LineData();

if(act.equals("getDataBP")) {
LineDataSet set = new LineDataSet(EntriesLineBP, "心拍数(bpm)");
set.setColor(Color.rgb(240, 238, 70));
set.setLineWidth(2.5f);
set.setCircleColor(Color.rgb(240, 238, 70));
set.setCircleRadius(5f);
set.setFillColor(Color.rgb(240, 238, 70));
set.setMode(LineDataSet.Mode.CUBIC_BEZIER);
set.setDrawValues(true);
set.setValueTextSize(12f);
//set.setValueTextColor(Color.rgb(240, 238, 70));
set.setAxisDependency(AxisDependency.RIGHT);
rightAxis.setAxisMaximum(set.getYMax() +10f);
rightAxis.setAxisMinimum((int) (set.getYMin()/5)*5 - 10f);

// set.setLabel("心拍数(bpm)");
d.addDataSet(set);
//Log.d("myTag","BP Line Chart is generated");

}else if(act.equals("getDataWt")) {
LineDataSet set = new LineDataSet(EntriesLineWt, "体脂肪率(%)");
set.setColor(Color.rgb(240, 238, 70));
set.setLineWidth(2.5f);
set.setCircleColor(Color.rgb(240, 238, 70));
set.setCircleRadius(5f);
set.setFillColor(Color.rgb(240, 238, 70));
set.setMode(LineDataSet.Mode.CUBIC_BEZIER);
set.setDrawValues(true);
set.setValueTextSize(12f);
rightAxis.setAxisMaximum(set.getYMax()+1f);
rightAxis.setAxisMinimum( (int) (set.getYMin()) - 1f);
rightAxis.setDrawGridLinesBehindData(true);
//set.setValueTextColor(Color.rgb(240, 238, 70));
set.setAxisDependency(AxisDependency.RIGHT);
rightAxis.setGranularity(1f);
d.addDataSet(set);
// Log.d("myTag","Wt Line Chart is generated");
}
return d;
}
private BarData generateBarData() {

BarDataSet set1 = new BarDataSet(EntriesBar, "体重(kg)");
set1.setColor(Color.rgb(61, 165, 255));
//set1.setValueTextColor(Color.rgb(61, 165, 255));
set1.setDrawValues(true);
set1.setValueTextSize(12f);
set1.setAxisDependency(AxisDependency.LEFT);
leftAxis.setAxisMaximum(set1.getYMax()+5f);
leftAxis.setDrawGridLinesBehindData(true);
leftAxis.setGranularity(5f);
leftAxis.setAxisMinimum((int) (set1.getYMin()/5) * 5f -5f );
/* BarDataSet set2 = new BarDataSet(entries2, "");
set2.setStackLabels(new String[]{"Stack 1", "Stack 2"});
set2.setColors(Color.rgb(61, 165, 255), Color.rgb(23, 197, 255));
set2.setValueTextColor(Color.rgb(61, 165, 255));
set2.setValueTextSize(10f);
set2.setAxisDependency(YAxis.AxisDependency.LEFT);*/

float groupSpace = 0.06f;
float barSpace = 0.02f; // x2 dataset
float barWidth = 0.45f; // x2 dataset
// (0.45 + 0.02) * 2 + 0.06 = 1.00 -> interval per "group"

// d.addDataSet(set1);
// BarData d = new BarData(set1, set2);
// d.groupBars(0, groupSpace, barSpace); // start at x = 0

BarData d = new BarData(set1 );
// d.addDataSet(set1);


d.setBarWidth(barWidth);
// make this BarData object grouped

return d;
}


private CandleData generateCandleData() {

CandleData d = new CandleData();

CandleDataSet set = new CandleDataSet(EntriesCDL, "血圧(mmHg)");
// set.setDecreasingColor(Color.rgb(142, 150, 175));
set.setDecreasingColor(Color.rgb(61, 165, 255));
set.setShadowColor(Color.DKGRAY);
set.setBarSpace(0.1f);
set.setValueTextSize(12f);
set.setDrawValues(true);
leftAxis.setAxisMaximum(set.getYMax()+5f);
leftAxis.setAxisMinimum((int) (set.getYMin()/5) *5 -10f);
leftAxis.setGranularity(5f);
d.addDataSet(set);

return d;
}



}

背景画像選択 (ImageActivity.java)

package com.example.HealthManagement;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.io.File;
import java.util.ArrayList;

import static android.os.Environment.DIRECTORY_DCIM;
import static android.os.Environment.DIRECTORY_PICTURES;
import static com.example.HealthManagement.CustomAdapter.get_Uri;


public class ImageActivity extends AppCompatActivity{

final private String[] items = {"LINE", "カメラ"};

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);

// Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
// setSupportActionBar(toolbar);


final ListView lv= (ListView) findViewById(R.id.lv);
lv.setAdapter(new CustomAdapter(ImageActivity.this,getData(0)));

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {


new AlertDialog.Builder(ImageActivity.this)
.setTitle("フォルダ選択")
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
lv.setAdapter(new CustomAdapter(ImageActivity.this,getData( which)));

}
})
.show();
}
});

lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id){

String uri = get_Uri(position);
SharedPreferences dataSave = getSharedPreferences("DataStore", MODE_PRIVATE);
SharedPreferences.Editor editor = dataSave.edit();
editor.putString("uri", uri);
editor.commit();

Intent intent = new Intent();
// intent.putExtra("uri", uri);
setResult(RESULT_OK, intent);

finish();
}




});

setTitle("写真を選ぶ");
}

private ArrayList<Spacecraft> getData(int pos) {
final String[] folders = {DIRECTORY_PICTURES+"/LINE/", DIRECTORY_DCIM+"/Camera/", DIRECTORY_PICTURES+"/はーくん/", DIRECTORY_PICTURES+"/はーくん(1)/"};
ArrayList<Spacecraft> spacecrafts=new ArrayList<>();
//TARGET FOLDER
File downloadsFolder= Environment.getExternalStoragePublicDirectory(folders[pos]);
// File downloadsFolder= (File) getExternalFilesDirs(folders[pos]); // folders[pos]);
// Log.d("myTag","(in ImageActivity) items = "+ items[pos]+ " folders " + folders[pos]);
Spacecraft s;

if(downloadsFolder.exists()){
//GET ALL FILES IN DOWNLOAD FOLDER
File[] files=downloadsFolder.listFiles();

//LOOP THRU THOSE FILES GETTING NAME AND URI
for (int i=0;i<files.length;i++){
File file=files[files.length-1-i];

s=new Spacecraft();
s.setName(file.getName());
s.setUri(Uri.fromFile(file));

spacecrafts.add(s);
// Log.d("myTag","(in ImageActivity) i= "+ i + " fileNage= " + file.getName());
}
}
return spacecrafts;
}



}

設定画面 (settingsActivity.java)

package com.example.HealthManagement;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.jetbrains.annotations.Nullable;
import org.json.JSONArray;
import org.json.JSONException;

import java.util.ArrayList;
import java.util.HashMap;

public class SettingsActivity extends AppCompatActivity implements View.OnClickListener {

SharedPreferences dataSave;
TextView tv_url;
EditText et_url;
EditText et_ulBP;
EditText et_llBP;
EditText et_ulPls;
EditText et_llPls;
EditText et_ulWt;
EditText et_llWt;
EditText et_ulbF;
EditText et_llbF;

Button bt_setting, bt_cancel;
DelayedProgressDialog progressDialog;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);


tv_url = findViewById(R.id.tv_url);
et_url = findViewById(R.id.et_url);
et_ulBP = findViewById(R.id.et_ulBP);
et_llBP = findViewById(R.id.et_llBP);
et_ulPls = findViewById(R.id.et_ulPls);
et_llPls = findViewById(R.id.et_llPls);
et_ulWt = findViewById(R.id.et_ulWt);
et_llWt = findViewById(R.id.et_llWt);
et_ulbF = findViewById(R.id.et_ulbF);
et_llbF = findViewById(R.id.et_llbF);

bt_setting = findViewById(R.id.bt_setting);
bt_setting.setOnClickListener(this);
bt_cancel = findViewById(R.id.bt_cancel);
bt_cancel.setOnClickListener(this);

dataSave = getSharedPreferences("DataStore", MODE_PRIVATE);
String url = dataSave.getString("url", null);
tv_url.setText(url);

//(takashi) String url = "https://script.google.com/macros/s/AKfycbymFRHqcNGS0iM3FesOJMQsQQIcbnS93FQw0YRFa5fsH8AiPxA/exec?";
//(mitsuko) String url = "https://script.google.com/macros/s/AKfycbxXhh4JWV1JD5rUuOipoGl3E_2wczUSTj07VtTRCDvsmkoh1B4/exec?";
// Log.d("myTag","url = " + url);

String ulBP = dataSave.getString("ulBP", null);
if(ulBP!=null){et_ulBP.setText(ulBP);}

String llBP = dataSave.getString("llBP", null);
if(llBP!=null){et_llBP.setText(llBP);}

String ulPls = dataSave.getString("ulPls", null);
if(ulPls!=null){et_ulPls.setText(ulPls);}

String llPls = dataSave.getString("llPls", null);
if(llPls!=null){et_llPls.setText(llPls);}

String ulWt = dataSave.getString("ulWt", null);
if(ulWt!=null){et_ulWt.setText(ulWt);}

String llWt = dataSave.getString("llWt", null);
if(llWt!=null){et_llWt.setText(llWt);}

String ulbF = dataSave.getString("ulbF", null);
if(ulbF!=null){et_ulbF.setText(ulbF);}

String llbF = dataSave.getString("llbF", null);
if(llbF!=null){ et_llbF.setText(llbF);}

}

@Override
public void onClick(View v) {

if(v==bt_setting){

SharedPreferences.Editor editor = dataSave.edit();

String url = et_url.getText().toString();
if( url.length() > 10) {
editor.putString("url", url);
//tv_url.setText(url);
}

String ulBP = et_ulBP.getText().toString();
editor.putString("ulBP", ulBP);

String llBP = et_llBP.getText().toString();
editor.putString("llBP", llBP);

String ulPls = et_ulPls.getText().toString();
editor.putString("ulPls", ulPls);

String llPls = et_llPls.getText().toString();
editor.putString("llPls", llPls);


String ulWt = et_ulWt.getText().toString();
editor.putString("ulWt", ulWt);

String llWt = et_llWt.getText().toString();
editor.putString("llWt", llWt);

String ulbF = et_ulbF.getText().toString();
editor.putString("ulbF", ulbF);

String llbF = et_llbF.getText().toString();
editor.putString("llbF", llbF);

editor.apply();

Toast.makeText(this,"設定変更しました",Toast.LENGTH_LONG).show();

Intent intent = new Intent();
setResult(RESULT_OK, intent);
finish();

}else if(v==bt_cancel){
Intent intent = new Intent();
setResult(RESULT_OK, intent);
finish();
}

}

}

マニフェスト

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.HealthManagement"
>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<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/AppTheme"
>
<activity android:name="com.example.HealthManagement.MainActivity"
android:screenOrientation="portrait"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.example.HealthManagement.InputBldPrssr"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
/>
<activity android:name="com.example.HealthManagement.InputWeight"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
/>

<activity android:name="com.example.HealthManagement.ChartActivity"
android:parentActivityName="com.example.HealthManagement.MainActivity"
android:theme="@style/AppTheme.NoActionBar"
>

<!-- The meta-data tag is required if you support API level 15 and lower -->
</activity>

<activity android:name="com.example.HealthManagement.SettingsActivity"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
>
</activity>
<activity android:name="com.example.HealthManagement.ImageActivity"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
>

</activity>
</application>

</manifest>

build.gradle(module.app)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.HealthManagement"
>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<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/AppTheme"
>
<activity android:name="com.example.HealthManagement.MainActivity"
android:screenOrientation="portrait"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.example.HealthManagement.InputBldPrssr"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
/>
<activity android:name="com.example.HealthManagement.InputWeight"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
/>

<activity android:name="com.example.HealthManagement.ChartActivity"
android:parentActivityName="com.example.HealthManagement.MainActivity"
android:theme="@style/AppTheme.NoActionBar"
>

<!-- The meta-data tag is required if you support API level 15 and lower -->
</activity>

<activity android:name="com.example.HealthManagement.SettingsActivity"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
>
</activity>
<activity android:name="com.example.HealthManagement.ImageActivity"
android:screenOrientation="portrait"
android:parentActivityName="com.example.HealthManagement.MainActivity"
>

</activity>
</application>

</manifest>

まとめ

このアンドロイドアプリは2回目だったので、開発に要した時間は約1週間程度でした。一度アプリを作ると同じパターンのソフトなら効率は上がります。

今回は、インターネット接続、チャート作成、リストビュー、内部ストレージ読書き、メニュー、タブフラグメントなどを使っています。

実際の実行形式プログラム(apk)と開発プロジェクトファイル(zip)を次の記事にてアップする予定です。

タイトルとURLをコピーしました