僕は普段スマホのメモアプリにはKeep メモを使っています。ただ、普段使わない自分にとって不要な機能もあり、もっとシンプルなメモアプリを使いたいなと思いました。Play Storeで良さげなメモアプリを探しましたが、広告がついていたり、見た目が気に入らなかったり。せっかくなので自分で作ってみることにしました。
スキル感について
普段の仕事ではWebエンジニアをしており、ReactやNext.jsなどを主に触っています。ネイティブアプリに関してはちんぷんかんぷんな状態で、Flutterについても同じです。ChatGPT先生を頼りまくることになりそうです。
作るものを決める
まずどんなメモアプリを作るかを決めます。自分がメモアプリを使うことを想像しながら、とりあえず紙に書いてみます。
テーマとしてはテキスト+画像ベースのノンストレスなメモアプリ
です。自分がメモアプリでストレスに感じていたのは、「リストに間違って触れて順序が勝手に変わってしまう」「不要な機能があるせいで必要な機能が探しにくい」だったので本当に自分が思う最低限だけの機能を入れます。
入力物は「テキスト」「箇条書き」「チェックリスト」「画像」を4つのみにします。太字や見出しは入れません。また、エディタに関してもスマホからだと使いづらいのでマークダウンは利用しない選択をしました。
※ちなみに結局画像の挿入は間に合わなかったので今回は実装してません。無念。
データも今の段階でどんな感じになりそうかだけ考えています。
そして画面ですがOOUIのモデリングっぽいことをしながら必要な画面や機能を書きました。もどきでしかないですが、これでもだいぶわかりやすくなりました。
ここまで考えたものを元に、Figmaでモックアップを作成します。
Figmaは昔良く触っていましたが知らないうちに便利な機能がいっぱい追加されていて驚きました。
これを実装しますが、こっちのほうが楽でいいじゃん!ってなったら改変してしてます。ここらへんはわりと適当です。
使う技術を決める
ここは純粋に興味がある/使いやすそうで決めているのでまだChatGPTは頼りません。色々自分で調べてみます↓
-
Flutter
- いつかマルチプラットフォームで開発するかもしれないので対応しているのが嬉しい
- React Nativeと悩みましたが、React Nativeは学生時代にバイトで触っていたので今回は触ってないFlutterが良かった
-
Flutter Quill
- Flutter用のリッチテキストエディタ
- 最低限必要な入力物に対して対応している
- エディタやツールバーのカスタマイズができる
-
sqflite
- Flutter用のSQLiteプラグイン
- 今回はローカルにデータ保存したいため、SQLiteを利用するため利用
ChatGPTを頼りながら実装する
作ったものはこちらになります。
以下では、実装時の重要な部分などを切り取って紹介しているので、アプリ全体のコードを見たい方はGitHubの方を御覧ください。
環境構築
Flutterの導入やプロジェクト作成はFlutter公式ドキュメントを見ながら実施しました。ちなみにエディタはVSCodeです。
とりあえずエディタを入れてみる
Flutter Quillの公式ドキュメントを見ながらとりあえずエディタが使えるようにします。
これはとっても簡単。
final QuillController _controller = QuillController.basic();
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,
),
),
)
],
),
)
不要なメニュー項目を消す
デフォルトのままだと、ツールバーに不要な項目があるためカスタマイズします。
ここはやり方がよく分からなかったのでChatGPT先生に聞いてみます。
ChatGPTが出してくれたコードは↓でした。
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のドキュメントを調べました。詳しく書かれていなかったので、実装も見つつ、メニュー項目を設定で変えられることがわかりました。これはわかるまで大変でした。
QuillToolbarConfigurations toolbarConfigurations() {
return const QuillToolbarConfigurations(
toolbarIconAlignment: WrapAlignment.spaceAround,
showAlignmentButtons: false,
showBackgroundColorButton: false,
showBoldButton: false,
...
)
}
ツールバーをキーボード表示時に移動させる
先程のChatGPTの質問に含めていましたが、こちらは完璧に回答してくれました。
ありがとう。ChatGPT先生。
アプリのデザインについて
ChatGPTを使って一番助かったのはスタイルの部分かもしれません。若干スタイルを定義する部分が違っていたりしますが、ほとんどコピペで動く物自体はできるので助かります。
↓教えてくれたもの
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でどうするべきか相談しています。
ツギハギコードになりがちなので適宜リファクタリングしないとしんどいですね。
SQLiteでMemoデータを取り扱う
実はSQLiteの選択はChatGPTに相談して決めました。
Shared Preferences
, File System
, SQLite
の選択肢を教えてくれて、それぞれメリット・デメリットまで聞いてないのに教えてくれました。とっても優しい。
設定方法も色々教えてもらいながら実装しました。
正直これは公式ドキュメント見ようよ案件ですね。
そっちのほうが早いし正確だと思います。
commit(DB設定)
commit(DBから取得したメモを表示して保存できるように)
DBのopen(), close()のタイミングは適切じゃないよなと思いつつ修正できてないです。
適切じゃないよなってわかっていればChatGPTに頼れるんですが、おそらく僕が適切じゃないと認識できていない所もあるんだろうなと思います。そこらへんのところが抜けてしまうのはChatGPT開発の怖いところだなと思います。とりあえず動くけど適切じゃないものができてしまう。
メモデータの新規作成&更新時にメモ一覧にも反映されるように
メモデータを保存したあと、メモページに戻っても変更が反映されていませんでした。
最初にDBから取得した値を使っているので当然です。再度DBから値を取得するようにしたいのでChatGPTに相談しました。
FutureBuilder
, Navigator.popNavigator.pop
, StatefulWidgetとsetState
を使った3つの方法を提示してくれました。最初はFutureBuilder
を使ったやり方を参考にしてみましたが、どうしてもうまくいかず、StatefulWidgetとsetState
をつかったやり方にしました。
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の構築
}
}
気になったのはsetState(() {})
で画面再描画させるやり方は一般的なんでしょうか?
このやり方が最適なのか分からず不安になりましたがいちょう動きました。(よくない)
完成したもの
メモ一覧
右下のボタンはFloatingActionButton
のデフォルトの形のままにしました。こだわりがなければデフォルトに従ったほうが良いと思ったので。
リスト長押しで削除できるようにしました。
他にも操作があればメニューを出したほうがいいのですが、現時点では削除以外ないので。
いきなり削除は怖いのでダイアログは出すようにしました。
メモ詳細
編集/保存の形式はモックアップから変更しました。
最初から編集できるようにして、右上に常時保存ボタンを出しました。
ツールバーの色は変更したかったですが、アイコンも変える必要があり面倒なのでそのままにしています。
エディタは普通に使えます。使ってみて色々不便なところは出てきました。
本文が空のときの入力判定エリアが1行分しかないためタップしづらかったり、スマホだとTAB入力が難しく、箇条書きリストを使いこなせないことなど。ここらへんは今後直していこうと思います。
ChatGPTを使ってみた感想
ほぼ初めてのネイティブアプリでしたが、ここまで詰まることなく実装できるのはやはりChatGPTがいるおかげだと思います。公式ドキュメントを読まなくなるようなことは避け、あくまで自分の補助として利用するのが適切だと思いました。今回はChatGPTに頼る比重が高めであまり真似はしないほうがいいと思いました。(質問の仕方が下手なのかもしれないけど)
- 良かった所
- なんでも質問できる
- とりあえず大体動くものや解決できる回答をくれる
- スタイルに関してはほとんどそのまま利用できる
- 質問内容を考えるのである程度自分の思考が整理できる
- 気になった所
- 情報が若干古く、そこの差分は自分で埋める必要がある
- そのコードが適切か判断する知識がないまま実装できてしまう