🚀

FlutterアプリをGWの5日間でAppStoreにリリースした時のこと

2020/09/19に公開

はじめに

こんにちは。
Android アプリエンジニアの trashfeed と言います。

この記事は、外出自粛の「STAY HOME」となったGW中に、庭にテントを張りながら1人開発合宿し、Flutterアプリ開発 〜 AppStoreリリース完了 までの5日間の記録になります。

やったこと

どんなアプリを作るのか?

気になるサイトの文章だけを保存し、素早く 読み返すことができるアプリを作ることにしました。

新しい技術をキャッチアップする時、Qiita や Medium の記事をブラウザのブックマーク、Pocketなどの「後で読む」系のサービスに追加するのですが、いざ読み返そうとするとページ全体を読むのはしんどくて積読される事が多くなりました。もっと重要な箇所だけをピンポイントでサクサク読み返す事ができるようなアプリになります。

ディレクトリ構造

ディレクトリ構造は、以下のように管理しました。

image.png

フォルダ名 説明
extensions 拡張関数。Color,Snapshotクラスの拡張を主に使用しました。
items Firestore側のTable構造とイコールになるオブジェクト
models ビジネスロジック。UIに依存しないビジネスロジック
pages 画面単位のUI。StatelessWidgetなどを継承した画面になります
repository DB(Firestore),SharedPreferencesの管理クラスなど
state 画面状態、画面のみのビジネスロジックなど
utils ログ出力など汎用クラス
widgets 画面に依存しない部品(一覧が空のときのメッセージ出力用のUIなど)

Firestore

Firestore側はセキュリティリスクが発生しそうなので詳細は省きますが、以下の3つのテーブルで構成されています。

テーブル名 説明
clips ユーザーが選択した文字列情報
bookmarks ブラウザのブックマーク
tags タグ

テーブル設計自体は非常にシンプルですが、インデックスは細かく張っています。
アプリ側からの取得条件と一致するインデックス(where,orderを含む)が完全一致しなければ、snapshotを利用してWidgetとバインドさせた時に、リアルタイムにデータが再表示されなくなり、結構ハマりポイントでした。

※下記は、DBの値を手動変更すると、アプリ側にリアルタイムに反映される一例。リアルタイムに表示されない場合、インデックスを再確認してみる(ascdescが一致していないなど)
1.gif

Firestoreは学ぶべき事が多いのですが、良書 実践 Firestore を事前に読んでいて、非常に参考になりました。

画面

画面一覧

アプリの要件が固まっていて大きな規模ではないので、画面数はそれほど多くはありません。
以下の画面を実装しました。

画面名 説明
ようこそ画面 最初に表示するイントロダクション画面
ブラウザ画面 ブラウザ。 ユーザーは気になる文章をコピーすると、アプリ側でタグ情報などを付与して自動保存する
クリップ一覧画面 保存したテキストの一覧画面。検索機能、シャッフル機能など
クリップ編集画面 保存したテキストにタグを付けたり、色分け、ピン留め、メモ書きなどできる
ブックマーク一覧画面 ブラウザのお気に入り一覧
ブックマーク編集画面 ブラウザのお気に入りの編集、削除、自分用のメモを残す事ができる
設定画面 あんまり設定する事はないが、テーマー等は変更できるように。

以降、画面単位で使用したパッケージも合わせて紹介します。(★マークは特におすすめパッケージになります!)

ようこそ画面

アプリインストール後、初回一度だけ表示する画面です。
これはパッケージ flutter_overboard を利用し実現しました。
掲載する画像・文言内容を決める事に集中すれば良く、コード自体はあまり実装せずに実現する事ができます。

パッケージ名 説明
flutter_overboard 画像と表示テキストを指定するだけで、イントロダクション画面が作成できる

tutorial.gif

ブラウザ画面

本アプリの要となるブラウザ画面です。
只、残念ながら webviewの webview_flutter が開発プレビュー版の為、挙動がおかしい箇所があるようです。クリティカルなissueも長くOpen状態になっているのですが(この為、本アプリではAndroid版のリリースは見合わせました)、最低限の機能は満たしているので採用しました。

パッケージ名 説明
flutter_webview ブラウザ。開発プレビュー版のため少し動作に難あり

browser.gif

画面右上の三点リーダーをタップして展開されるメニューは、長いテキストも表示できるように、Flutter純正の BottomSheet(showModalBottomSheet) を利用しました。こちらも非常に簡単に実装する事ができます。

browser_popupmenu.gif

クリップ一覧

ユーザーが選択した文章を一覧表示する画面です。
記憶の定着を行いやすくするため、シャッフル 機能など実装していますが、基本的にFlutter純正のWidgetだけで開発しました。検索機能のみ simple_search_bar を利用して実現しました。

パッケージ名 説明
simple_search_bar★ 必要最小限の検索機能をAppBarに提供する

clip_list_search.gif

クリップ編集

一番対応に時間がかかった画面です。 保存したテキストに、タグピンコメント 等が設定できる画面になります。

特に、タグを自由につけるというUIは一般的ですが、DB設計と合わせて実現するのに丸1日かかってしまいました。
これも flutter_tagging というお世話になったパッケージがあり、これがなければもっとかかっていたと思います。
Flutterは標準Widgetで実装できない場合でも、packageが充実している為助かります。只、必要なpackageがない場合、ネイティブ側の開発が必要になるので、Flutter採用を決める時には、考慮が必要な点だなと思います。

パッケージ名 説明
flutter_tagging★ タグの選択補完、登録、削除をサポートする

clip_edit_tag.gif

ブックマーク一覧・編集

この頃になると、流用できるWidgetが揃ってきて、ブックマーク一覧・編集画面の実装は全く迷いなく実装できました。Firestoresnapshotの恩恵もあり、非常にスピーディーでした。

bookmark.gif

設定画面

パッケージ shared_preferences_settings で大半の設定画面を簡単に実装する事ができました。Androidの設定画面を作る時のUI側preferences.xmlだけを実装する印象に近く、基本的なUIや保存処理等は意識せずに実装する事ができます。

機能単位で多くなる設定項目(例:SNSログインは各サービスごとにトグルスイッチを表示する)なども、別画面として遷移させる事も柔軟、かつ、簡単に実装する事が可能でした。

最初は別のパッケージ preferences を使用してたのですが、アイコン表示が標準でサポートされておらず(もしかしたら勘違いかも!)こちらのパッケージに乗り換えました。

パッケージ名 説明
shared_preferences_settings★ 設定画面のUI・実装が行える

setting.gif

画面系の実装は以上になります。
Flutterは、Widgetの共通化やExtensionの実装など、リファクタリングすればするほど、後続の開発が楽になるエンジニアに優しいプラットフォームだなと感じた5日間でした。

最後に紹介したパッケージについてまとめておきます

その他の開発について

ナビゲーション

ナビゲーションには、ff_navigation_bar を利用しました。
pub.devnavigationで検索 すると表示されるパッケージをいくつか試してみましたが、移行コスト自体は低いので自由に確認してアプリにあったナビゲーションを利用する事が出来そうです。

bottom_navigation.gif

又、各タブ毎に履歴を残したい(通常はタブ表示時に、buildが発生するので画面が再表示される)場合は、IndexedStackを利用することで解決できます。
※こちらは state_notifier を使用した場合の例です。


        child: IndexedStack(
          index: Provider.of<AppState>(context, listen: false).index,
          children: _pages,
        ),

状態管理について

state_notifier

State の管理は、定番(?)の state_notifierproviderを中心にしました。
change_notifier or state_notifier の選択に迷ったのですが、状態ロジック を分離できる state_notifier を採用しました。 (Flutter state_notifierいい感じなので使ったほうが良いですよ が非常に参考になりました。有難うございました!)

1 年ほど前に Flutter を軽くキャッチアップした時は、 ScopedModelBLoCInheritedWidgetStreamBuilder などが主流(?)でしたが、現在は Provider を中心に、全体的により開発しやすい状態になっているなというのが第一印象です。

Widget について

Widgetは、数自体も多いのですが、とても魅力的なものばかりでどれを採用するか迷ってしまいます。只、開発を終え振り返ってみると、よく使うWidgetは次のとおりでした。※これはあくまで自分の場合で、人によって必ず違うと思います。

Widget 属性 説明
★Text 文言 Text("保存")
★Icon アイコン Icon(Icons.check)
★Container alignment 位置(縦・横) Alignment.topLeft
padding パディング EdgeInsets.all(10)
margin マージン EdgeInsets.all(10)
★Column mainAxisAlignment 「縦」方向の子Widgetの開始位置 MainAxisAlignment.start
crossAxisAlignment 「横」方向の子Widgetの開始位置 CrossAxisAlignment.start
★Row mainAxisAlignment 「横」方向の子Widgetの開始位置 MainAxisAlignment.start
crossAxisAlignment 「縦」方向の子Widgetの開始位置 CrossAxisAlignment.start
Wrap alignment 位置(縦・横) Alignment.topLeft
direction 子Widgetの方向 1
spacing 子Widget同士のメイン方向の間隔 1
runSpacing 子Widget同士のクロス方向の間隔 1
IconButton icon アイコン Icon(Icons.check)
onPressed タップ処理 onPressed: () {}
★FlatButton.icon label 表示ラベル Text("保存")
icon アイコン Icon(Icons.check)
onPressed タップ処理 onPressed: () {}
Expanded flex 子Widgetの比率(固定幅) 3
Flexible flex 子Widgetの比率(自動幅) 3
GestureDetector onTap タップ処理 onPressed: () {}
特に「★」はよく使いました。

Packageについて

パッケージは、「pub.dev」で探したり、Qiita や Medium で紹介されているものを使用しています。特に今後のアプリ開発でも是非使いたいなと思っているものについて紹介します。

flutter_tagging

  • https://pub.dev/packages/flutter_tagging
  • タグ選択時の入力サジェストや、新規であれば新規登録の為のアクションを提供してくれます。
  • アプリで「タグ」の登録、選択を行う必要がある場合、きっとまた使用すると思います。
    2_tag.gif

shared_preferences_settings

  • https://pub.dev/packages/shared_preferences_settings
  • 設定画面はほぼこのパッケージだけで実装を行う事ができました。
  • 設定画面内で、別の設定画面へ遷移する事もグルーピングも可能です。
  • アイコンも表示できてUI的にも想像通りのパッケージでした。
    3_setting.gif

flutter_icons

  • https://pub.dev/packages/flutter_icons
  • アイコン好きに是非利用してほしいパッケージです。
  • Flutterで利用できるアイコンが一気に広がります。
  • 使えるアイコンがとても多いので、(完全にイコールではないですが) react-native-vector-icons directory でアイコン名を検索して利用しました。
    image.png

dynamic_theme

  • https://pub.dev/packages/dynamic_theme
  • テーマ変更(カラー・ダークテーマ)に利用しました。
  • providerで自力で実装する事もできそうですが、パッケージを使うともっと簡単に実装できます。
    4_theme.gif

ストアリリース

最後の難関ストアリリースです。
コロナで審査に時間がかかる事は覚悟していたのですが、予想より早く提出から1.5日ほどでストア公開となりました。

状態 日時 備考
審査待ち 2020年5月4日 20:25
審査中 2020年5月6日 1:03
リジェクト 2020年5月6日 1:25 Guideline 2.3.7 メタ-データリジェクト
審査待ち 2020年5月6日 1:53 サブタイトルを変更して再提出
審査中 2020年5月6日 1:58
配信準備完了 2020年5月6日 4:00

Discussion