【個人開発】ReactNative + expoでシンプルな家計簿アプリを作ってみた

# はじめに

自分1人でアプリを作り切ったことがなく、作ってみたかったため趣味で作ってみました。
SIer企業でPMとして働いているため、技術も詳しくなるとめちゃくちゃ強いだろうな～、将来技術を深めるキャリアに進みたいな～、アプリでお金稼いでみたいな～など色々下心はあったりします。あとシンプルに自分のアプリを持ってたらかっこいい。

# 作ったもの　Tag型家計簿アプリ -TagMo-
個人開発をするにあたって、まず最初はわかりやすいものを作ろう。せっかく作るなら自分で使えるものを作ろう。ということでシンプルな家計簿アプリを作りました。インスタなどのように、タグ感覚で金額を入力、可視化できるようにしています。
### コンセプト
- 位置情報による周辺の小売店・飲食店などタグ付け、支払い手法、分類をタグ付け、支出の記録を容易にする
- 作成は無料にこだわる(DB、地図情報の取得は有料APIを用いていない)
- とにかくシンプルなUI

### 機能一覧
ざっくり機能一覧と対応する画面を書いておきます。
- Home
  - 位置情報による近隣の小売店・飲食店などの表示
  - 表示された店名か、EC・交通・その他を押すことで金額など入力画面に遷移
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/735e3529-af69-298c-beb4-e5dbb4eb0e19.jpeg" width=40%>
  
  - 店舗、決済手段を指定して、金額・利用カテゴリを入力し支払い記録保存
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/3d479f1a-de4b-c42e-f48e-da1c4301f294.jpeg" width=40%>
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/98da62df-64fe-dc37-17aa-b31f74c14d2f.jpeg" width=40%>

  - Settingsに遷移し、プッシュ通知の設定

  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/66c9b96d-1211-d7d8-d7be-ea5804476605.jpeg" width=40%>

  - Othersに遷移、その他の記載（今後投げ銭的な機能搭載したい）
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/e5c59ee0-92eb-5c7e-4344-a2ad6a98e667.jpeg" width=40%>


- History
  - 保存した支払い記録の閲覧
  - 表示期間指定
  - 指定された表示期間の合計金額表示
  - 支払い記録の編集画面への遷移
 
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/369f2245-4da1-a324-9c24-397538108761.jpeg" width=40%> 

  
  - 支払い記録の編集・削除


  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/b70ac77a-8f95-aab6-5415-0f1bccbe2f79.jpeg" width=40%>

- Balance
  下記の種類のタブを表示
  - 分類
    - カテゴリ毎の合計金額と比率を表す円グラフの表示

    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/5020b981-ba3f-71db-8446-8f121197b7c5.jpeg" width=40%>

  - 支払い方法
    - 支払い方法毎の合計金額と比率を表す円グラフの表示

    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/08651886-6594-9c2a-f9d8-b4c0e9dffea3.jpeg" width=40%>

  - 支出推移
  　- 年毎の支出遷移を表示

    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/7f75dccc-5976-e692-93b0-8ab6eb35cfc1.jpeg" width=40%>

### 画面遷移図
アプリの見た目とそれらの画面遷移の関係について示しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3134184/18ccd1fb-27b6-ff33-3d8d-0490085a6029.png)

### データ
今回、テーブルはトランザクションテーブル1つだけなので簡単に記載しておきます。
マスタテーブル的なものはなしです。
本アプリでは以下のテーブルに書き込み、取得します。

| 列名                | データ型       | 必須 　| 説明                                  |
|---------------------|---------------|------|---------------------------------------|
| `id`               | INTEGER       | 必須 | 各取引を識別する一意の ID。オートインクリメント。 |
| `transaction_date` 　　| TIMESTAMP     | 必須 | 取引日時を記録。                      |
| `payment_location` | TEXT          | 必須 | 支払場所（例: スーパー、カフェ）。     |
| `payment_method`   | TEXT          | 必須 | 支払方法（例: 現金、クレジットカード）。|
| `category`         | TEXT          | 必須 | 支出のカテゴリ（例: 食費、交通費）。  |
| `amount`           | INTEGER       | 必須 | 支出金額。                            |
| `memo`             | TEXT          | 任意 | 任意のメモ。空欄可。                  |


# 使用技術

### 主な使用技術一覧
私が参考にしていたリンクも貼っておきます。

- [React Native](https://reactnative.dev/)
  - React Navigation
  - react-native-community/datetimepicker(カレンダー機能)
  - Async Storage(プッシュ通知のON/OFF、通知時間の保存に利用)
- [Typescript](https://typescriptbook.jp/)
- [expo](https://docs.expo.dev/)
  - expo-router(画面遷移)
  - expo-sqlite(DB)
  - expo-location(位置情報取得)
  - expo-notifications(プッシュ通知)
- [Overpass API(OpenStreetMap のデータを扱う API)](https://wiki.openstreetmap.org/wiki/JA:Overpass_API)
- [axios(overpass api のための HTTPライブラリ)](https://axios-http.com/)

### ReactNative
スマホアプリを作成する手段はいくつかありましたが、以下の要素からReact Nativeを選択しました。
- クロスプラットフォームである
　→　現在iosにしか対応させていないが、これを学ぶだけでandroidアプリにも対応できる
- webアプリケーションにも対応できる
　→　今後そっちにも手を出していきたい
- Typescript(javascript)を使用する
　→　個人的にjavascriptが使えたため、キャッチアップコストが低いと考えた
### expo
React Nativeでアプリケーション開発を行うために様々な機能を有したプラットフォーム。
一覧にも書いたようにライブラリも充実していたり、実機での動作確認も簡単に行えます。

https://fintan-contents.github.io/mobile-app-crib-notes/react-native/learn/basic-concepts/what-is-expo

### DB

今回コンセプトの1つに「作成は無料にこだわる」とあるので、Firebaseなどのクラウドサービスは利用していないです。
そのため、ローカルにデータ保存ができるSQLiteとAsync Storageを利用しています。これらは以下のように使い分けています。
- [SQLite](https://docs.expo.dev/versions/latest/sdk/sqlite/)
 →　RDBを扱うため、メインのデータベース操作に利用
- [Async Storage](https://docs.expo.dev/versions/latest/sdk/async-storage/)
 →　key value storeを扱うため、プッシュ通知の状態管理のために利用している(通知時間、通知設定ON/OFF)
### Overpass API
DBと同じで、コンセプトに則るためGoogle mapなどのサービスは今回利用していないです。無料で利用できるOverpass APIというものを使いました。wikipediaのように誰もが地図情報を編集し、情報を埋めていくといようなものなので、フォーマットが揃っていない・情報が足りないなどありそうなのは少し気になりました。ただ無料なので使うことにしました。
詳しい情報は、他に記事を書いているのでそちらを見てみてください。

https://qiita.com/Mayoneeeez/items/f32c5d6047855fbe17b2

### 利用ツールなど
他に利用したツールの一覧を記載しておきます。
- [VScode](https://code.visualstudio.com/) →　エディタ
- [figma](https://www.figma.com/)　→　アプリのプロトタイプに利用
- [chatGPT](https://chat.openai.com/)　→　エラーハンドリングでお世話になりました
- [Miro](https://miro.com/)　→　画面遷移図用
- [OneNote](https://www.microsoft.com/ja-jp/microsoft-365/onenote/digital-note-taking-app)　→　メモ用

# 成果物

https://github.com/Mayoneeeez/TagMo-with-react-native




# 作ってみた感想
めちゃくちゃ楽しかったです。
シンプルで簡単なアプリではありましたが、開発に使用する技術の学習、機能を実現するための調査、自分で考えたものが形になる感覚は楽しいものです。
また、こんなシンプルなアプリでも非同期処理、DBどうする？、Git管理、Reactの理解など学ぶことがたくさんあって非常に意義があったなと感じました。


# 今後
appstoreにも出してみたいなーなどと考えたりしています。また、次はwebアプリケーションを作って、自分でインフラ構築までできたら面白いし、業務などに活かすことができるのかなと想像が膨らんでますね。
この記事も誰かの参考になったらとても嬉しいです。App storeに出そうかね。

# 参考
### React Native
公式サイト以外だと以下のサイトがとても参考になりました。
react nativeだけでなく、他の学習リソースも有益な情報が多かった印象です。

https://fintan-contents.github.io/mobile-app-crib-notes/

### Git
 成果物管理にgithubを使用した。gitの使い方って最初意外と難しいので、視覚的にイメージできる以下のゲームで学習した。めっちゃわかりやすい。
https://ohmygit.org/

### 開発メモ(私の奮闘が見て取れます。参考になるかわかりませんが一応)
・Redux
https://qiita.com/FarStep131/items/ad834facc57a443a9dc3#:~:text=%E3%81%84%E3%82%8B%E3%81%AF%E3%81%9A%E3%81%A7%E3%81%99%E3%80%82-,Redux%20%E3%81%A8%E3%81%AF,%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82
・React, ReactNative
https://soranoba.net/programming/react-native-code-sharing
・React Native 0.71よりTypeScriptが事実上の標準言語に
https://codezine.jp/article/detail/17139
・Expo　アプリの動作確認ローカルでやるやつ
・React-native-cli 非推奨らしい
https://ichinari.work/reactnative_20200714/
・Reactnative, typescript導入、ディレクトリ構造参考
https://zenn.dev/chot/articles/537c99ea098add
https://qiita.com/sinokuma/items/ac59a2705715998aeaa0
https://zenn.dev/mybest_dev/articles/c0570e67978673
・React概要
https://www.kagoya.jp/howto/it-glossary/develop/react/
・Material UI → 画面デザイン部分で使えそう
https://mui.com/
https://qiita.com/Bashi50/items/8964cc55c596e51fcbbe
https://qiita.com/Sotq_17/items/7c7ab9880597336b3ee5
https://ralacode.com/blog/post/react-material-ui/
・CSS 追加font
https://www.tohoho-web.com/css/rule/font-face.htm
・Truetype, Opentype
https://www.dynacw.co.jp/support/support_faq_detail.aspx?qid=501&fcid=200
・Styled-components
https://deku.posstree.com/react/create-react-app/styled-components/
・React Native Elements → 検索バー、チェックボックスとかいろいろできる
https://high-career.jp/2022/02/01/react-native-elements/
・バージョンが違うライブラリが横行しているため注意
・React, Typescript勉強しないといけないと感じる
・やはり公式ドキュメント
・bottomTab
https://qiita.com/minimumskills/items/4fa150c98b7afc0ab79c
react native navigationはいらない問題
・yarnでinstallした環境瞬間バグった　→　構築しなおした
・NavigationContainerは親子関係の中で原則1つ、ネストは推奨されない→今回Home⇔Amount間では利用
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>で検索バー→ほかの箇所押す→キーボード引っ込む実装可能
・https://chatgpt.com/share/225f6569-4692-4b17-8d9d-781fcf7e59cd
→navigatorオブジェクトはcomponentでも受け取れる。引数に入れたくないときに活用。それぞれのnavigatorの引数の型を定義できる。
・Appの設定は\node_modules\expo-router\entry.js
・React Navigationで画面遷移簡単にできる
→Stack, Tab, Drawer
→普通の画面遷移、よくある下タブ遷移、設定とかの画面に引き出してくる感じの画面遷移(設定とか)
https://fintan-contents.github.io/mobile-app-crib-notes/react-native/learn/basic-concepts/react-navigation-basics/
・https://fintan-contents.github.io/mobile-app-crib-notes/
→fintanつよい
・DOM、仮想DOM
DOMは変更点があった場合、全体を再構築
仮想DOMは変更点があった場合、差分だけを追加削除する→こっちの方が早い
・useEffect
副作用の管理？第二引数を見て、それが変更されたときにフックする？
第二引数を[]にすると最初のレンダーのみで実行される
Return でコンポ―ネントアンマウント時実行
https://qiita.com/waewae96/items/ca8c7a83eddec04e646b
・ カスタムフック→useではじまり、ほかのフックを呼び出せるjavascript関数
・ useEffectの依存配列
依存配列の要素は値の変更があった場合useEffectが起動
→仕組みとしては、レンダー時に常に起動されるが、配列の中が変更されていない場合スルーらしい
https://qiita.com/honey32/items/62edf5165aced7d0c4bf
・文法的に合っていても、自分が定義したtype定義の型精査でエラーを出してしまうときがある
Home画面からdistance, shopName, shopLocationNameが投げられていても、Amout画面で受ける方が後者2つしかTypeで許容していないときdistanceを呼ぼうとするとエラーが出た
・下記つまづいたところ、loadList, refreshingの更新でそれらを更新するときも非同期なため、下記useEffectに反応するuseEffectを作成するのが確実
```typescript
useEffect(() => {
    const fetchData = async () => {
      try {
        const result = await db.getAllAsync<ListItemProps>(
          `
        SELECT 
          category, 
          SUM(amount) as total_amount 
        FROM amount_list
        GROUP BY category
        ORDER BY total_amount DESC
        `
        );
        // amount をカンマ区切りにフォーマット
        const formattedResult = result.map((item) => ({
          ...item,
          total_amount: formatNumberWithCommas(item.total_amount),
        }));
        await setListData(formattedResult);
      } catch (error) {
        console.error("Error fetching data:", error);
      }
    };
    fetchData();
    setRefreshing(false);
    console.log();
    console.log("balanceがloadListの変更に反応しているか確認");
    console.log(loadList);
    console.log(listData);
    console.log();
  }, [refreshing, loadList]);
```
・変数初期化→変数への代入と普通に記述すると非同期処理により変数初期化前に変数への参照が起こりエラーになることがある
→　chartData初期化、useEffect内で代入を行った時に起こった
useStateなどによるフックでの初期化は、コンポーネントがレンダリングされる際に初期化されるため、上記問題が起こらない
https://chatgpt.com/share/c0fbb23b-69c8-4cda-a21b-69f13644f3bf
・useStateのsetは非同期なため、下記の場合setが完了する前にDB更新を行ってしまい、整合性が取れない場合がある
```typescript		
onPress: async () => {
    console.log("押下後formData");
    console.log(formData);
    await setValue(route);
    console.log("setValue後formData in onUpdateHistory");
    console.log(formData);
    await DBApi.updateAmountList(formData);
    navigation.navigate("History");
    setLoadList(!loadList);
    Alert.alert("保存が完了しました");
  },
 ```

 ### クレジット
本記事記載のアプリケーション
では、[OpenStreetMap](https://www.openstreetmap.org/) の地図データを使用しています。  
このデータは、[Creative Commons Attribution-ShareAlike 2.0](https://creativecommons.org/licenses/by-sa/2.0/) ライセンスの下で提供されています。  
© OpenStreetMap contributors