【個人開発】共有可能なメモアプリを作った話 使用パッケージも紹介【Flutter】

##　はじめに
シンプルでありながら多彩な用途に使える共有可能なメモアプリ **Notespod** を開発、リリースしました。~~現在のところAndroid版のみとなっています。~~
iOS版もリリースしました。記事はこちら([Qiita](https://qiita.com/taki0010/items/80e13e423674bbba7406))([Zenn](https://zenn.dev/taki/articles/084eac295b7820))。

iOS版に合わせたandroid版の仕様変更に伴い、こっちの記事も修正しました。スクリーンショットは全てiOS版のものに差し替えています。


https://play.google.com/store/apps/details?id=com.karas.notespod

https://apps.apple.com/jp/app/notespod/id1600436936


![ss_1+2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369656/f2b2f171-4a36-99a8-fde5-cbf7dca15857.png)

##　経歴
といっても実務経験はなく、趣味で React(Next.js) + Django Rest Framework でブログを作ったことがあるだけです。次はどうしようかと考えたときに、まだ未経験なモバイル開発をやってみようと思いました。
##　Flutterかい？
はいそうです。勉強を始めたのが今年の1月で、本格的に開発を始めたのが4月ごろだったので、完成まで8か月くらいです。正直、ちゃんと仕様を決めて開発に臨んだわけではなく、割と行き当たりばったりで作ってきましたが、結果的にはなんとか完成までこぎつけることができました（1か月くらいリファクタリングしてた気がする:grimacing:）
##　ふーん、どんなアプリ？
簡単に言えば、「多彩なファイルを添付できて**フレンドと**共有することもできるメモアプリ」 です。Evernote や Google Keep に近いかもしれませんが、詳しくはパッケージの紹介と共に説明させていただきます。




##　使用したパッケージ

###　Flutter Markdown [→](https://pub.dev/packages/flutter_markdown "flutter_markdown")
マークダウンで書けます。アプリを始めると複数のサンプルが表示されるので、それを見ていただければ慣れてないユーザーでもだいたいの記法はわかるんじゃないかと思います。



###　Flutter image compress [→](https://pub.dev/packages/flutter_image_compress "flutter_image_compress")
画像を添付できますが、その画像を圧縮するのに使っています。同じ解像度のWebP画像に圧縮してますが、容量が半分くらいになって助かります。別の端末でも閲覧できるように画像はcloud storageにアップロードされます。ダウンロードする際には下記のFlutter Cache Managerを使っています

### Flutter Cache Manager [→](https://pub.dev/packages/flutter_cache_manager "flutter_cache_manager")
主にcloud storage のファイルをキャッシュするのに使っています。だいたい以下のような感じです。キャッシュはデフォルトの設定では30日か200ファイルをそれぞれ超過すれば古い順から削除されます

```dart:
     FutureBuilder<String>(
          future: FirebaseStorage.instance.ref(path).getDownloadURL(),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return const CircularProgressIndicator();
            }
             final url = snapshot.data;
             return StreamBuilder<FileResponse>(
                    stream: DefaultCacheManager()
                        .getFileStream(url!, withProgress: true),
                    builder: (context, snapshot) {
                      if (snapshot.hasError) {
                        return const Icon(Icons.error_outline);
                      } else if (!snapshot.hasData ||
                          snapshot.data is DownloadProgress) {
                        return const CircularProgressIndicator();
                      } else {
                        final file = snapshot.data as FileInfo;
                        return Image.file(
                          file.file,
                          fit: BoxFit.cover,
                        );
                      }
                    });
                 })
```








### Google Maps for Flutter [→](https://pub.dev/packages/google_maps_flutter "google_maps_flutter")
場所もメモできます。地図上にマーカーを立てるくらいですが、一応ダークモードに対応しました。下記のブログ記事がわかりやすかったです。
:point_right: [How To Implement a Customized Google Map with Flutter](https://enlear.academy/how-to-implement-a-customized-google-map-with-flutter-d6fb40534cda "How To Implement a Customized Google Map with Flutter")
必要があれば座標を渡して地図アプリで開くこともできます。また、画像としてSNS等にシェアすることもできます。
![ss_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369656/a5b82a03-e336-1aeb-0478-a682a035965b.png)


###　Youtube Player IFrame [→](https://pub.dev/packages/youtube_player_iframe "youtube_player_iframe")
Youtube動画もメモできます。このウィジェットはYoutube動画をIframeで表示してくれます。URLか動画IDをコピペするだけで貼れます。Youtubeとは別に、mp4やwebmにも対応してますが（それぞれ3MBまで）大抵はYoutubeが貼れれば間に合うような気がします。
![Simulator Screen Recording - iPhone 12 Pro Max - 2022-02-12 at 15.45.06.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369656/58547b88-52c1-ed91-e034-47c7fa966159.gif)

###　Flutter Sound Lite [→](https://pub.dev/packages/flutter_sound "flutter_sound")
オーディオもメモできます。ストレージ内のファイルだけでなく、その場の録音にも対応しています。フレンドと共有する場合は月当たりの容量制限がありますが、動画同様にプランをアップグレードしていただくことで大幅に緩和されます



### Podcast Search [→](https://pub.dev/packages/podcast_search "podcast_search")
ポッドキャストもメモできます。デフォルトではiTunesの検索APIが使われています。番組のURLを含めた詳細な情報はもちろん、各国の人気ランキングも取得できます。





###　Metadata Fetch [→](https://pub.dev/packages/metadata_fetch "metadata_fetch")
Webページもメモできます。このパッケージにURLを渡すとOGPをはじめとしたメタデータを拾ってきてくれます。気になってるテーマに関連するサイトを同じメモにまとめておくとチェックしやすくていい感じです。必要であればブラウザで開くこともできます。
![Simulator Screen Recording - iPhone 12 Pro Max - 2022-02-12 at 15.22.06.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369656/7a87e017-889b-a5fa-9f68-cd249784537d.gif)





### animate_do [→](https://pub.dev/packages/animate_do "animate_do")
手軽に様々なアニメーションを適用できます。例えば記事一覧の表示で、記事を1個づつフワフワッと表示する場合、下記のような実装で表現できます

```dart:
        ListView.builder(
            itemCount: posts.length,
                itemBuilder: (context, index) {
                      final post = posts[index];
                      final int deley = index * 100.toInt();
                         return FadeInUp( // ← ここ
                           duration: const Duration(milliseconds: 500),
                           from: 20,
                           delay: Duration(milliseconds: deley),
                           child: _Post(post:post)
                            );
                         },
                     ),
```


###　palette_generator [→](https://pub.dev/packages/palette_generator "palette_generator")
画像を渡すとその画像の主張色を返してくれます。ユーザーが貼った画像の上にテキストやアイコンを表示する必要があるんですが、画像の色合いに合わせてそれらUIの色を白か黒にするために使用してます。まず`Color`が返ってくるので、そのRGBの合計を出します。その値がRGBの70%にあたる`535.5`（(255+255+255)*0.7）を上回れば黒、下回れば白といった具合にやってます。例えば日の丸を渡したら赤が返ってくるのでテキスト色が白になって白地の部分と同化してしまったりとか、いつも正しい配色になるわけじゃないんですが、だいたいの場面ではうまく機能してくれます

```dart:
  final PaletteGenerator palette =
      await PaletteGenerator.fromImageProvider(Image.file(file).image);

  final color = palette.dominantColor!.color;
  final rgbList = [color.red, color.blue, color.green];
  final total = rgbList.reduce((a, b) => a + b);
  final result = total > 535 ? Colors.black : Colors.white;
  return result;
```



###　Flutter Toast [→](https://pub.dev/packages/fluttertoast "fluttertoast")
メッセージをトースト表示してくれる便利なパッケージですが、2021/12/10 現在、android11以降では背景色が反映されない＆表示位置が下に固定されるという不具合があるようです。pubspec.yaml で以下のようにしてgitから取得すると背景色に関しては修正されます。

```yaml:
  fluttertoast:
    git:
      url: https://github.com/ponnamkarthik/FlutterToast.git
```

### Hand signature [→](https://pub.dev/packages/hand_signature "hand_signature")
署名できます！
だから何と言われたらそれまでですが:rolling_eyes:
送られてきたメモにハートでも描かれてたらちょっと嬉しい……かも……:love_letter:
署名はsvg形式でFirestoreのフィールドに書き込んでます。おそらく真っ先に使われなくなる機能……っ！


#### 以下のパッケージは外しました
##### ~~Awesome Notifications~~
##### ~~paged_vertical_calendar~~
##### ~~screen_capture_event~~




##　特徴
ひとつのメモにつき、テキストだけでなく画像、動画、音声、場所、Webサイト、グラフ、PDF、Youtube、Podcast といった様々な種類のデータを自由な組み合わせで合計3個まで添付できます（プレミアムプランは30個まで:trident:） 個人で好きに使っていただくのもいいんですが、**フレンドと**メモをやりとりすることもできます。受け取ったメモは編集も可能です。要はメールに近いかと思います（ただ別のフレンドに転送することはできません）



##　フレンドシステムなんだね
メールアドレスでユーザー登録するメモアプリが多いなか、本アプリはフレンドコードで気軽に登録できるシステムです。誰かと誰かを繋げるのはSNSに任せるとして、その繋がった人々だけで**誰にも邪魔されない閉じたコミュニケーション**ができるアプリになっています。

実装に関してはこちらに軽くまとめました。

https://qiita.com/taki0010/items/6abdf278c79e05a51bd6

ぜひ多くの方々にお使いいただければと思います（もちろん自分用のメモアプリとして使っていただくのもお勧めです:thumbsup:）




##　今後の予定
:one: iOS版
:two: 添付データの拡張

の順で考えてますが、Macがない……:confused:
Codemagicを使えば行けるらしい……が……ただでさえアップルは色々面倒というイメージが強い上に、このライブラリの山…… :raised_hands:（お手上げのポーズ） しかし:two: からやると:one: がどんどん面倒になっていくという……。
とりあえずAndroid版の経過を見ながら考えていこうかなと……:eyes:

#### iOS版リリースに伴う追記
android版から約2か月くらいで:one: iOS版をリリースできました。ついでに:two: もやりました（グラフとPDF）

審査に関してはかなり身構えていたんですが、UIや権限部分を2〜3個突っ込まれただけで通ったので拍子抜けしました。記事にするほどのネタもなかったのでiOS版の記事では触れていません。


##　終わりに
お読みいただきありがとうございます。飛ばした方も、ここまでスクロールしていただきありがとうございます。



なんだかフレンド云々でアピールしてしまいましたが、**基本は普通のメモアプリです。**実は、**フレンドシステムは後付けです。**今さらただのメモアプリが使われるわけないよなあという事実に気づき、開発途中で当初の予定にない機能（フレンドシステム含む）をいろいろ付け足してしまっただけです。


こういうのは自信のなさの表れなんだろうなあと思います。自信がないから（最初から）色々つけ足そうとする……コアな部分に自信があるならさっさと仕上げてリリースしましょう。機能追加ならそのあとにまた考えましょう。途中で（コレ大丈夫か……？）って不安に駆られるのは個人開発あるあるだと思いますが、そこで機能追加の方向に行くのはかなり安易な発想だと思いますし、少なくとも近道にはならないはずです。実際、このアプリの場合、もっとスリムな段階でひと区切りつけていればiOS版の対応も楽だったはずです。

とはいえ、結果的には悪くないアプリになったと思います:fist: よかったら使ってやってください。