FlutterアプリをGWの5日間でAppStoreにリリースした時のこと
はじめに
こんにちは。
Android アプリエンジニアの trashfeed と言います。
この記事は、外出自粛の「STAY HOME」となったGW中に、庭にテントを張りながら1人開発合宿し、Flutterアプリ開発 〜 AppStoreリリース完了
までの5日間の記録になります。
やったこと
- Flutter をキャッチアップし、GW中にアプリをリリースした
- リリースしたアプリ
どんなアプリを作るのか?
気になるサイトの文章だけを保存
し、素早く 読み返す
ことができるアプリを作ることにしました。
新しい技術をキャッチアップする時、Qiita や Medium の記事をブラウザのブックマーク、Pocketなどの「後で読む」系のサービスに追加するのですが、いざ読み返そうとするとページ全体を読むのはしんどくて積読される事が多くなりました。もっと重要な箇所だけをピンポイントでサクサク読み返す事ができるようなアプリになります。
ディレクトリ構造
ディレクトリ構造は、以下のように管理しました。
フォルダ名 | 説明 |
---|---|
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の値を手動変更すると、アプリ側にリアルタイムに反映される一例。リアルタイムに表示されない場合、インデックスを再確認してみる(asc
とdesc
が一致していないなど)
Firestore
は学ぶべき事が多いのですが、良書 実践 Firestore を事前に読んでいて、非常に参考になりました。
画面
画面一覧
アプリの要件が固まっていて大きな規模ではないので、画面数はそれほど多くはありません。
以下の画面を実装しました。
画面名 | 説明 |
---|---|
ようこそ画面 | 最初に表示するイントロダクション画面 |
ブラウザ画面 | ブラウザ。 ユーザーは気になる文章をコピーすると、アプリ側でタグ情報などを付与して自動保存する |
クリップ一覧画面 | 保存したテキストの一覧画面。検索機能、シャッフル機能など |
クリップ編集画面 | 保存したテキストにタグを付けたり、色分け、ピン留め、メモ書きなどできる |
ブックマーク一覧画面 | ブラウザのお気に入り一覧 |
ブックマーク編集画面 | ブラウザのお気に入りの編集、削除、自分用のメモを残す事ができる |
設定画面 | あんまり設定する事はないが、テーマー等は変更できるように。 |
以降、画面単位で使用したパッケージも合わせて紹介します。(★マークは特におすすめパッケージになります!)
ようこそ画面
アプリインストール後、初回一度だけ表示する画面です。
これはパッケージ flutter_overboard
を利用し実現しました。
掲載する画像・文言内容を決める事に集中すれば良く、コード自体はあまり実装せずに実現する事ができます。
パッケージ名 | 説明 |
---|---|
flutter_overboard | 画像と表示テキストを指定するだけで、イントロダクション画面が作成できる |
ブラウザ画面
本アプリの要となるブラウザ画面です。
只、残念ながら webviewの webview_flutter
が開発プレビュー版の為、挙動がおかしい箇所があるようです。クリティカルなissueも長くOpen状態になっているのですが(この為、本アプリではAndroid版のリリースは見合わせました)、最低限の機能は満たしているので採用しました。
パッケージ名 | 説明 |
---|---|
flutter_webview | ブラウザ。開発プレビュー版のため少し動作に難あり |
画面右上の三点リーダーをタップして展開されるメニューは、長いテキストも表示できるように、Flutter純正の BottomSheet(showModalBottomSheet)
を利用しました。こちらも非常に簡単に実装する事ができます。
クリップ一覧
ユーザーが選択した文章を一覧表示する画面です。
記憶の定着
を行いやすくするため、シャッフル
機能など実装していますが、基本的にFlutter純正のWidgetだけで開発しました。検索機能のみ simple_search_bar
を利用して実現しました。
パッケージ名 | 説明 |
---|---|
simple_search_bar★ | 必要最小限の検索機能をAppBar に提供する |
クリップ編集
一番対応に時間がかかった画面です。 保存したテキストに、タグ
、色
、ピン
、コメント
等が設定できる画面になります。
特に、タグを自由につけるというUIは一般的ですが、DB設計と合わせて実現するのに丸1日かかってしまいました。
これも flutter_tagging
というお世話になったパッケージがあり、これがなければもっとかかっていたと思います。
Flutterは標準Widgetで実装できない場合でも、packageが充実している為助かります。只、必要なpackageがない場合、ネイティブ側の開発が必要になるので、Flutter採用を決める時には、考慮が必要な点だなと思います。
パッケージ名 | 説明 |
---|---|
flutter_tagging★ | タグの選択補完、登録、削除をサポートする |
ブックマーク一覧・編集
この頃になると、流用できるWidgetが揃ってきて、ブックマーク一覧・編集画面の実装は全く迷いなく実装できました。Firestore
のsnapshot
の恩恵もあり、非常にスピーディーでした。
設定画面
パッケージ shared_preferences_settings
で大半の設定画面を簡単に実装する事ができました。Androidの設定画面を作る時のUI側preferences.xml
だけを実装する印象に近く、基本的なUIや保存処理等は意識せずに実装する事ができます。
機能単位で多くなる設定項目(例:SNSログインは各サービスごとにトグルスイッチを表示する)なども、別画面として遷移させる事も柔軟、かつ、簡単に実装する事が可能でした。
最初は別のパッケージ preferences を使用してたのですが、アイコン表示が標準でサポートされておらず(もしかしたら勘違いかも!)こちらのパッケージに乗り換えました。
パッケージ名 | 説明 |
---|---|
shared_preferences_settings★ | 設定画面のUI・実装が行える |
画面系の実装は以上になります。
Flutterは、Widgetの共通化やExtensionの実装など、リファクタリングすればするほど、後続の開発が楽になるエンジニアに優しいプラットフォームだなと感じた5日間でした。
最後に紹介したパッケージについてまとめておきます
その他の開発について
ナビゲーション
ナビゲーションには、ff_navigation_bar
を利用しました。
pub.dev
で navigationで検索 すると表示されるパッケージをいくつか試してみましたが、移行コスト自体は低いので自由に確認してアプリにあったナビゲーションを利用する事が出来そうです。
又、各タブ毎に履歴を残したい(通常はタブ表示時に、build
が発生するので画面が再表示される)場合は、IndexedStack
を利用することで解決できます。
※こちらは state_notifier
を使用した場合の例です。
child: IndexedStack(
index: Provider.of<AppState>(context, listen: false).index,
children: _pages,
),
状態管理について
state_notifier
State の管理は、定番(?)の state_notifier と provider
を中心にしました。
change_notifier
or state_notifier
の選択に迷ったのですが、状態
と ロジック
を分離できる state_notifier
を採用しました。 (Flutter state_notifierいい感じなので使ったほうが良いですよ が非常に参考になりました。有難うございました!)
1 年ほど前に Flutter を軽くキャッチアップした時は、 ScopedModel
、BLoC
、InheritedWidget
、StreamBuilder
などが主流(?)でしたが、現在は 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
- タグ選択時の入力サジェストや、新規であれば新規登録の為のアクションを提供してくれます。
- アプリで「タグ」の登録、選択を行う必要がある場合、きっとまた使用すると思います。
shared_preferences_settings
- https://pub.dev/packages/shared_preferences_settings
- 設定画面はほぼこのパッケージだけで実装を行う事ができました。
- 設定画面内で、別の設定画面へ遷移する事もグルーピングも可能です。
- アイコンも表示できてUI的にも想像通りのパッケージでした。
flutter_icons
- https://pub.dev/packages/flutter_icons
- アイコン好きに是非利用してほしいパッケージです。
- Flutterで利用できるアイコンが一気に広がります。
- 使えるアイコンがとても多いので、(完全にイコールではないですが) react-native-vector-icons directory でアイコン名を検索して利用しました。
dynamic_theme
- https://pub.dev/packages/dynamic_theme
- テーマ変更(カラー・ダークテーマ)に利用しました。
-
provider
で自力で実装する事もできそうですが、パッケージを使うともっと簡単に実装できます。
ストアリリース
最後の難関ストアリリースです。
コロナで審査に時間がかかる事は覚悟していたのですが、予想より早く提出から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