ところどころ詰まるたびに疎遠になってなかなか進まなかった記録アプリ...ようやくやってみたかったことがある程度できたので記事にまとめることに。
が、これまた実装しようと思っていることをやりきるにはまだしばらく時間かかりそうだし記事も長大になりそうなので、今回は中間報告挟みます。
作りたいたいのは、いまExcelでやってる家計簿てきなことと、アプリでやってる消費時間の記録(リズムケアというやつ)、の二つを兼ね合わせてちょこっと分析も足したもの。時は金なりっていうし、時間とお金記録してグラフ化したり分析したら生活の見直しになるかな(ということで暫定のアプリ名はLifeReportです、後述のデータベース名もこれにしました)、という感じ。
現在できてるのは大体ここまで。
画質ひどい笑
前回gifどうやって準備したんだっけ...
とりあえず家計簿的な部分を簡単に実装しました。まだ開発途中も途中なので、全体のコード公開は完成時に...部分的にポイントになったところだけ貼り付けていきます。
今回主に注目した機能とそのために用いたパッケージは以下。
データベース:expo-sqLite
https://docs.expo.io/versions/latest/sdk/sqlite/
SQLiteそのものの使い方は下記など。
https://www.tutorialspoint.com/sqlite/index.htm
https://www.sqlitetutorial.net/
グラフ:react-native-chart-kit
https://www.npmjs.com/package/react-native-chart-kit
https://aboutreact.com/react-native-chart-kit/
あと、地味に苦戦した日付の扱いではmomentを使いました。
https://medium.com/better-programming/using-moment-js-in-react-native-d1b6ebe226d4
下記はほぼ前回から 引き続き。
・アプリのRoutingはreact-navigation
・パーツ類はNative base
・State管理はunstated
では中身の紹介です。相変わらず未熟なので、改善案を絶賛募集中です。
### expo-sqlite でのデータ管理 ###
1. データベースの生成
const LifeReport = SQLite.openDatabase('LifeReport')
これはそのまま...()内がデータベース名で、存在しなければ自動生成されます。
2.テーブルの生成
LifeReport.transaction(tx =>{
tx.executeSql(
"CREATE TABLE IF NOT EXISTS expenses (" +
"ID text primary key not null," +
"Date text," +
"Category text," +
"Amount integer," +
"Remark text" +
");"
);
},
() => {console.log('fail')},
() => {console.log('success_tableCheck')}
);
基本的な書き方は、データベース変数.transactionからのアロー関数でexcuteSql、その中身に実際の
SQLで実行されるコードを書きます。ここは好みですが、自分は上記のように文字列を+で連結する形にして行を増やしていきました。
3.データポイントの挿入
onDoneAddExpense = (id, date, category, amount, remark) => {
if (amount !== '') {
LifeReport.transaction(tx => {
tx.executeSql(
"INSERT INTO expenses" +
"(ID, Date, Category, Amount, Remark)" +
" VALUES (?, ?, ?, ?, ?);" ,
[id, date, category, amount, remark]
);
},
() => {console.log('fail')}, // callback at failure
() => {this.updateExpenses(), this.getTodaysExpenses()}, // callback at success
);
}
};
特徴的?なのは
SQLのコードの中に?を入れて、executeSqlの次の引数に配列を入れることでその中身が順番に代入されていくことでしょう。executeSqlを閉じた先で、失敗時と成功時のコールバック関数を指定できます。
4.特定の日付間かつ指定のカテゴリの合計金額を日ごとに持ってくる
LifeReport.transaction(tx => {
tx.executeSql(
"SELECT Date, SUM(Amount) Subtotal " + // SUM() Variable_name
"FROM expenses " +
"WHERE Category = ? AND Date BETWEEN ? AND ? " +
"GROUP BY Date " +
";",
[category, lowerDate, upperDate],
(_, { rows: { _array } }) => {
this.setState({
dailyExpenses_dates: _array.map(x => x.Date),
dailyExpenses_amounts: _array.map(x => x.Subtotal)
})
}
)
},
() => {console.log('fail')},
() => {console.log('success_daily')}
)
そしてデータを持ってくる部分SELECT、FROM、WHERE、GROUP BYなんかは
SQLの基本ですね。クエリに対してのレスポンスの受け取り方は公式のをそのままです...単にresをconsole.log()とかで見ればわかりますがネストがほんのり複雑なので深追いせず転用です。
一点補足しておくと、一つのFunction(例えば3.のonDoneAddExpense)の中にtransactionを複数入れても、実行されるのは最初の一つだけの模様です。それなのにtransactionの中のコールバックは成功の方が実行されるので、成功したはずなのに
SQLの実行結果が反映されない...?といったことに。実行してないから失敗してないし成功っしょってな、紛らわしいわ。複数の
SQLコードを実行したい場合、transactionのアロー関数の先に複数つっこむ必要があるみたいです(公式のExampleのコードがその書き方)。...という理解なのですが、詳しい方、できればぜひ正しくわかりやすい解説をば。
### react-native-chart-kitでのグラフ化 ###
まだあまりこのパッケージを使いこなせないままです...ここでは4.で持ってきた日付(dailyExpenses_dates)をlabels(x軸)と日ごと合計金額(dailyExpenses_amounts)をdatesets.data(y軸)へ入れています。
<LineChart
data={{
labels: this.props.container.state.dailyExpenses_dates,
datasets: [
{
data: this.props.container.state.dailyExpenses_amounts,
strokeWidth: 2,
},
],
}}
width={Dimensions.get('window').width - 16}
height={450}
fromZero= {1}
yAxisLabel={'AUD '}
verticalLabelRotation = {45}
chartConfig={{
backgroundColor: '#1cc910',
backgroundGradientFrom: '#eff3ff',
backgroundGradientTo: '#efefef',
decimalPlaces: 0,
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
style: {
borderRadius: 5,
},
}}
style={{
marginVertical: 8,
borderRadius: 5,
}}
/>
...ほとんどSQLliteのメモ書きですね。ただ、
SQLiteのReact Nativeの実装例はネットで浅く調べる限りだとそこまで出てこないので、多少の価値はあるかなと。
あとそもそもAsyncStorageではなく
SQLiteにする意義があるのか?みたいなとこ疑問に思いながらやってみたのですが、やっぱりデータの操作は
SQLでいじってそれから取り出す方が楽ですよね。transaction頻繁にするより一回で全部取り出してjsの書き方でデータ操作する方が処理は速いのでしょうか?データ量が多くなった時のこのあたりのバランスは不明です。
### 後半戦の課題 ###
・Picker系とかもうちょっと使いやすくできないかなぁ...日付やら時間やら期間やら。
・お察しの通り変数名がごちゃごちゃです...消費時間の方も取り組んでいて、整理しないと自分でも行方不明になりそう。
・今回消費金額と時間という別のものを似てるけど若干違うフォーマットで記録していくことになるけど、データテーブルは一つにすべきか?今はテーブルをそれぞれ分けてる。
・データテーブルと少し関連して、データ操作の関数はお金と時間の変数で似た機能でもそれぞれ分けてる(データポイントの挿入、本日分のデータ取得、など)が、それに合わせてunstatedのコンテナも分けるか?
・グラフに表示するデータの設定の仕方でデータポイント無くなりエラーを起こすなど。。。
・一日当たりの消費時間はPieChartの予定だけど、うまく表示できず。。。
・react-native-chart-kit以外のグラフパッケージで使いやすくもう少し自由の利くのあるかな?
・分析は
SQLでどこまでできる?外部パッケージはどんなのがある?
### 総括 ###
・グラフは最低限出来たけどもうちょっときれいにしたい
・分析はまだこれから、分析のア
イデア自体も実装手段も
...とまぁ駆け足で記事というよりはメモ書きな感じだけど、取り急ぎ!
2020/03/22 追記
記録アプリを公開までもっていって、個別機能の振り返りもしたので追加メモ。
State管理
unstatedによる単一containerでのstate中央管理にはだいぶ慣れ、自分の開発規模ではこれで十分だと実感。
これからもまだまだ使っていくだろうな。その分reduxは遠ざけたままになるけれど。
ストレージ
expo-sqliteはsqliteのコードを直書きしなきゃいけないのが最初好きになれなかったけれど、そもそもがシンプルな記述のみだから慣れてくるとそんなに気にならなくなったかな。
前回の翻訳アプリでのAsyncStorageと比べて、やはりsqliteのがデータベースとして使いやすいと実感できた。
まるまるJSONにして保存や取り出しを行ってjsで頑張って整形するより、集約したデータベースから個別の機能や動作に合わせて必要なデータを持ってくる方がすっきりできる、当たり前だろうけど。
react-native-chart-kit
多少発展的な使い方もしてみたけれど、やっぱ限界感じる。詳しくは下記。
tkd708.hatenablog.com