ChatGPTに頼りながらFlutterでメモアプリを自作してみた

僕は普段スマホのメモアプリには[Keep メモ](https://play.google.com/store/apps/details?id=com.google.android.keep&hl=ja&gl=US)を使っています。ただ、普段使わない自分にとって不要な機能もあり、もっとシンプルなメモアプリを使いたいなと思いました。Play Storeで良さげなメモアプリを探しましたが、広告がついていたり、見た目が気に入らなかったり。せっかくなので自分で作ってみることにしました。

### スキル感について

普段の仕事ではWebエンジニアをしており、ReactやNext.jsなどを主に触っています。ネイティブアプリに関してはちんぷんかんぷんな状態で、Flutterについても同じです。ChatGPT先生を頼りまくることになりそうです。

### 作るものを決める

まずどんなメモアプリを作るかを決めます。自分がメモアプリを使うことを想像しながら、とりあえず紙に書いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/1ae01b17-6d04-2b1c-6201-414a2a969ab8.png)

テーマとしては`テキスト+画像ベースのノンストレスなメモアプリ`です。自分がメモアプリでストレスに感じていたのは、「リストに間違って触れて順序が勝手に変わってしまう」「不要な機能があるせいで必要な機能が探しにくい」だったので本当に自分が思う最低限だけの機能を入れます。

入力物は「テキスト」「箇条書き」「チェックリスト」「画像」を4つのみにします。太字や見出しは入れません。また、エディタに関してもスマホからだと使いづらいのでマークダウンは利用しない選択をしました。

※ちなみに結局画像の挿入は間に合わなかったので今回は実装してません。無念。

データも今の段階でどんな感じになりそうかだけ考えています。
そして画面ですが[OOUIのモデリング](https://www.sociomedia.co.jp/7279)っぽいことをしながら必要な画面や機能を書きました。もどきでしかないですが、これでもだいぶわかりやすくなりました。

ここまで考えたものを元に、Figmaでモックアップを作成します。
Figmaは昔良く触っていましたが知らないうちに便利な機能がいっぱい追加されていて驚きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/2f0b4e28-98ab-af43-80de-20b05879d17c.png)

これを実装しますが、こっちのほうが楽でいいじゃん！ってなったら改変してしてます。ここらへんはわりと適当です。


### 使う技術を決める

ここは純粋に興味がある/使いやすそうで決めているのでまだChatGPTは頼りません。色々自分で調べてみます↓

- [Flutter](https://flutter.dev/)
    - いつかマルチプラットフォームで開発するかもしれないので対応しているのが嬉しい
    - React Nativeと悩みましたが、React Nativeは学生時代にバイトで触っていたので今回は触ってないFlutterが良かった
- [Flutter Quill](https://pub.dev/packages/flutter_quill)
    - Flutter用のリッチテキストエディタ
    - 最低限必要な入力物に対して対応している
    - エディタやツールバーのカスタマイズができる
- [sqflite](https://pub.dev/packages/sqflite)
    - Flutter用のSQLiteプラグイン
    - 今回はローカルにデータ保存したいため、SQLiteを利用するため利用

### ChatGPTを頼りながら実装する

作ったものはこちらになります。

https://github.com/asasigure1209/my-memo-app

以下では、実装時の重要な部分などを切り取って紹介しているので、アプリ全体のコードを見たい方はGitHubの方を御覧ください。

#### 環境構築

Flutterの導入やプロジェクト作成は[Flutter公式ドキュメント](https://docs.flutter.dev/get-started/install/windows)を見ながら実施しました。ちなみにエディタはVSCodeです。

#### とりあえずエディタを入れてみる

[commit](https://github.com/asasigure1209/my-memo-app/commit/25f8c0e07ff5f6fe3cd9154ecf039ff6e5adb472)

Flutter Quillの公式ドキュメントを見ながらとりあえずエディタが使えるようにします。
これはとっても簡単。

```dart
final QuillController _controller = QuillController.basic();
```

```dart
body: QuillProvider(
  configurations: QuillConfigurations(
    controller: _controller,
    sharedConfigurations: const QuillSharedConfigurations(
      locale: Locale('ja'),
    ),
  ),
  child: Column(
    children: [
      const QuillToolbar(),
      Expanded(
        child: QuillEditor.basic(
          configurations: const QuillEditorConfigurations(
            readOnly: false,
          ),
        ),
      )
    ],
  ),
)
```

#### 不要なメニュー項目を消す

[commit](https://github.com/asasigure1209/my-memo-app/commit/b1d82931e4b4e058f1fac4b4bc0aca40a5f0550c)

デフォルトのままだと、ツールバーに不要な項目があるためカスタマイズします。
ここはやり方がよく分からなかったのでChatGPT先生に聞いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/5486f797-1d85-84f8-b5d1-4a721671ba8e.png)

ChatGPTが出してくれたコードは↓でした。

```dart
QuillToolbar.basic(
  controller: _controller,  // QuillControllerを指定
  toolbarIconAlignment: WrapAlignment.spaceAround,
  multiRowsDisplay: false,
  buttons: [
    // 箇条書きボタン
    BulletListButton(icon: Icons.format_list_bulleted),
    // チェックボックスボタン
    CheckboxButton(icon: Icons.check_box),
    // その他カスタマイズ可能なボタンやスタイルを追加
  ],
)
```

ここでちょっとした問題が。ChatGPT先生は2022年1月までの情報しか持っていませんでした(実装当時)。
そのためFlutter Quillのバージョンが低く、最新と実装が異なる状態でした。

これはどうしようもないので[Flutter Quillのドキュメント](https://github.com/singerdmx/flutter-quill/blob/master/CHANGELOG.md#900-dev-6)を調べました。詳しく書かれていなかったので、実装も見つつ、メニュー項目を設定で変えられることがわかりました。これはわかるまで大変でした。

```dart
QuillToolbarConfigurations toolbarConfigurations() {
    return const QuillToolbarConfigurations(
        toolbarIconAlignment: WrapAlignment.spaceAround,
        showAlignmentButtons: false,
        showBackgroundColorButton: false,
        showBoldButton: false,
        ...
    )
}
```

#### ツールバーをキーボード表示時に移動させる

[commit](https://github.com/asasigure1209/my-memo-app/commit/8cab9b7d6f29c6293a6582b2679d1ee976a3bb7f)

先程のChatGPTの質問に含めていましたが、こちらは完璧に回答してくれました。
ありがとう。ChatGPT先生。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/136cb0e2-900d-27fe-0219-b366f454095c.png)

#### アプリのデザインについて

ChatGPTを使って一番助かったのはスタイルの部分かもしれません。若干スタイルを定義する部分が違っていたりしますが、ほとんどコピペで動く物自体はできるので助かります。

↓教えてくれたもの

```dart
MaterialApp(
  theme: ThemeData(
    // アプリ全体の基本テーマ設定
    primarySwatch: Colors.blue,
    textTheme: TextTheme(
      // ここでフォントスタイルをカスタマイズ
    ),
    appBarTheme: AppBarTheme(
      color: Colors.green, // AppBarの背景色
      textTheme: TextTheme(
        headline6: TextStyle(
          color: Colors.white, // AppBarのタイトルフォントの色
          fontSize: 20,
        ),
      ),
    ),
  ),
  home: MyHomePage(),
);
```

スタイルを色々コピペしたあと、共通化できるものについては定数で取り扱うなどの対応はしています。こちらもChatGPTくんにDartでどうするべきか相談しています。
ツギハギコードになりがちなので適宜リファクタリングしないとしんどいですね。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/31f4b613-3002-3e98-9ba7-5b09424e079c.png)

#### SQLiteでMemoデータを取り扱う

実はSQLiteの選択はChatGPTに相談して決めました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/c09c9e20-2212-e481-94c0-79da719f9f1d.png)

`Shared Preferences`, `File System`, `SQLite`の選択肢を教えてくれて、それぞれメリット・デメリットまで聞いてないのに教えてくれました。とっても優しい。

設定方法も色々教えてもらいながら実装しました。
正直これは公式ドキュメント見ようよ案件ですね。
そっちのほうが早いし正確だと思います。

[commit(DB設定)](https://github.com/asasigure1209/my-memo-app/commit/383a08e28a57a26122b78e55f28980b43bea491e)
[commit(DBから取得したメモを表示して保存できるように)](https://github.com/asasigure1209/my-memo-app/commit/2a962db19f7d67644e15835a4678f6815e0489fe)

DBのopen(), close()のタイミングは適切じゃないよなと思いつつ修正できてないです。
適切じゃないよなってわかっていればChatGPTに頼れるんですが、おそらく僕が適切じゃないと認識できていない所もあるんだろうなと思います。そこらへんのところが抜けてしまうのはChatGPT開発の怖いところだなと思います。とりあえず動くけど適切じゃないものができてしまう。

#### メモデータの新規作成&更新時にメモ一覧にも反映されるように

メモデータを保存したあと、メモページに戻っても変更が反映されていませんでした。
最初にDBから取得した値を使っているので当然です。再度DBから値を取得するようにしたいのでChatGPTに相談しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/f1fdedc3-1c65-0a16-05df-57cf6e920270.png)

`FutureBuilder`, `Navigator.popNavigator.pop`, `StatefulWidgetとsetState`を使った3つの方法を提示してくれました。最初は`FutureBuilder`を使ったやり方を参考にしてみましたが、どうしてもうまくいかず、`StatefulWidgetとsetState`をつかったやり方にしました。

```dart
class MemoListPage extends StatefulWidget {
  @override
  _MemoListPageState createState() => _MemoListPageState();
}

class _MemoListPageState extends State<MemoListPage> {
  List<Memo> memos;

  @override
  void initState() {
    super.initState();
    loadMemos();
  }

  void loadMemos() async {
    memos = await getMemosFromDatabase();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    // UIの構築
  }
}
```

[commit](https://github.com/asasigure1209/my-memo-app/commit/ce4ed60a701fdc698ebffb1c91ea5e37d57088c3)

気になったのは`setState(() {})`で画面再描画させるやり方は一般的なんでしょうか？
このやり方が最適なのか分からず不安になりましたがいちょう動きました。（よくない）

### 完成したもの

#### メモ一覧

右下のボタンは`FloatingActionButton`のデフォルトの形のままにしました。こだわりがなければデフォルトに従ったほうが良いと思ったので。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/243c5503-de58-aa54-6d6a-b6a7c592e833.png">

リスト長押しで削除できるようにしました。
他にも操作があればメニューを出したほうがいいのですが、現時点では削除以外ないので。
いきなり削除は怖いのでダイアログは出すようにしました。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/72c9d7de-2c2b-44c8-7981-52080d8fb313.png">

#### メモ詳細

編集/保存の形式はモックアップから変更しました。
最初から編集できるようにして、右上に常時保存ボタンを出しました。
ツールバーの色は変更したかったですが、アイコンも変える必要があり面倒なのでそのままにしています。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/453e53da-b26a-f455-ceeb-8e1e48161764.png">

エディタは普通に使えます。使ってみて色々不便なところは出てきました。
本文が空のときの入力判定エリアが1行分しかないためタップしづらかったり、スマホだとTAB入力が難しく、箇条書きリストを使いこなせないことなど。ここらへんは今後直していこうと思います。


### ChatGPTを使ってみた感想

ほぼ初めてのネイティブアプリでしたが、ここまで詰まることなく実装できるのはやはりChatGPTがいるおかげだと思います。公式ドキュメントを読まなくなるようなことは避け、あくまで自分の補助として利用するのが適切だと思いました。今回はChatGPTに頼る比重が高めであまり真似はしないほうがいいと思いました。（質問の仕方が下手なのかもしれないけど）

- 良かった所
    - なんでも質問できる
    - とりあえず大体動くものや解決できる回答をくれる
    - スタイルに関してはほとんどそのまま利用できる
    - 質問内容を考えるのである程度自分の思考が整理できる
- 気になった所
    - 情報が若干古く、そこの差分は自分で埋める必要がある
    - そのコードが適切か判断する知識がないまま実装できてしまう