保護猫好きになったので1週間でFlutter/Firebaseフル活用して無料アプリを作った話

概要
----
コロナの影響でリモートワークになったタイミングで、保護猫を飼い始めました。

これまで犬派だったのに、いつの間にか虜になってしまいました。
気づいたら保護猫専用アプリまで作り出したのでその技術的な紹介をしたいと思います。

![screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26833/51f7403a-1948-49e7-4c4b-449ca1d1e435.png)

構成
----
個人で時間も限られているのでmBaaSをフル活用し、Firebase + Flutterにてアプリを構成することにした。

* Flutter 2.0.3
* Firebase Analytics
* Firebase Authentication
* Firebase Cloud Firestore
* Firebase Storage
* Firebase Hosting
* Firebase Crashlytics

Flutterはちょっと触ったことあったが、かなり良さげでOSSも充実している模様。
Firebaseとの連携は[FlutterFire](https://firebase.flutter.dev/)を利用したらめちゃ楽でした。(さすがGoogle)
※ Remote Configも入れたかったが開発時点(2021/04)ではバグのため導入ができなかったため断念

### Flutter

マルチプラットフォームでアプリ開発する上でとてもよかったです。
DartもJS Likeでとっつきやすく、iOSのstoryboardやらXcodeの重さに苦労しまくっていた自分としては神がかったツールでした。(もうあの頃に戻れない)

ドキュメントを順番に読んでいくと理解しやすいと思います。

https://flutter.dev/

### FlutterFire

FlutterとFirebaseの連携のためのプラグインでドキュメントが充実しています。
導入手順がわかりやすいので入れたい機能をそのまま対応するだけで基本問題なかったです。

https://firebase.flutter.dev/

### Firebase Authentication

今回SNSログインを入れるため、Twitter,Google,Apple認証をそれぞれ導入しました。
マルチ認証(1ユーザーに複数SNS連携)もサポートしており、それぞれの導入手順(SNS側の設定)も記載があったのでそこまで困らなかったです(Twitterを除いて)

Twitterは公式がOSSから撤退している関係上、FlutterFireでの説明ではうまく導入できませんでした。
筆蹟時点でも最終更新が2019年...
色々試した結果[flutter_twitter](https://pub.dev/packages/flutter_twitter)を元にForkした自作パッケージを利用することにした

```pubspe.yaml
#  flutter_twitter_login: ^1.1.0  # ビルドが通らなかった
#  flutter_twitter: ^1.1.3        # ビルドは通るが、UIWebviewを利用しApple申請できない
  flutter_twitter:
    git:
      url: https://github.com/ateliee/flutter_twitter_login
```

Twitterアプリの申請は[この辺り](https://dev.classmethod.jp/articles/twitter-api-approved-way/)を参考にするとすぐに利用できました。

### Firebase Cloud Firestore

Firebaseの新しいNo SQL DBがいつの間にかリリースされていたので導入してみた。
Realtime Databaseに比べて多少とっつきにくかったが、比較的簡単に導入できます。
Indexやトランザクション処理もサポートしており、今の所良さげ
RDBに慣れ切っている自分としては、スキーマ構成が一番悩ましかったです(作っては消してを繰り返す)
どんなものでも入れる反面、初期のスキーマ設計はとても重要になる印象でした。

### Firebase Storage
写真の保存先に利用。5GBまでの制限化で無料で使える。
保存先のパスをFirestoreに保存するのだが、合わせてurlも保存してあげる
(Firebase Functionを利用すればパスから自動で付与できるが、有料プランに切り替える必要があったので今回はクライアントで設定してあげた)

イメージとしては下記みたいな感じ

```dart
    String userId = FirebaseAuthService().currentUser().uid;
    var result = await FirebaseCloudStorageService().upload(
        '$dir/$filename',
        bytes,
        SettableMetadata(
          contentType: mimeType,
          customMetadata: {
            'orgName': orgFilename,
            'userId': userId,
          },
        )
    );
    var doc = FirebaseFirestoreService().collection('users').doc(userId);
    value.update({
      'thumbnail_path': result,
      'thumbnail_url': await FirebaseCloudStorageService().downloadUrl(result),
      'thumbnail_width': uploadFile.metadata.width,
      'thumbnail_height': uploadFile.metadata.height,
      'updated_at': DateTime.now(),
    });
```

### Firebase Hosting

これもCLIで簡単にデプロイできる
手動で実行する場合、自分はfirestone,storageのruleなども合わせて利用しているのでhostingのみをデプロイする

```bash
# ログイン
firebase login
# 初期化
firebase init
# デプロイ
firebase deploy --only hosting
```

### Firebase Crashlytics

クラッシュレポートツールだが、お金をかけれるなら[sentry](https://sentry.io/from/crashlytics/)のほうが良さげ。
今回は無料で導入も簡単であったため入れておく(少し前までFirebaseはFabricを利用していたが、サポート終了のため混同しないよう注意)

### Jenkins

[fastlane](http://fastlane.tools/)使ってみたいなーと思いつつ、環境があったのでJenkinsを利用。
ビルドからApp Store Connect,Google Play Consoleへ自動アップロードする仕組みも用意があったのでそれを利用した。


Flutterの便利そうなところ
----

### [flutter_native_splash](https://pub.dev/packages/flutter_native_splash)を利用してスプラッシュ画像を更新

```bash
flutter pub run flutter_native_splash:create
```

### [flutter_launcher_icons](https://pub.dev/packages/flutter_launcher_icons)でアプリアイコンを更新

```bash
flutter pub run flutter_launcher_icons:main
```

### [font_awesome_flutter](https://pub.dev/packages/font_awesome_flutter)

FontAwesomeのFlutterバージョン。アプリが1ランクアップする。

### [wechat_assets_picker](https://pub.dev/packages/wechat_assets_picker)

[multi_image_picker](https://pub.dev/packages/multi_image_picker)を使おうとしたが、開発終了していたため代用で利用
multi_image_pickerを利用する場合、開発時点ではmetadataでエラーになったが利用したい人は下記で使えるかと思います。

```pubspec.yaml
  multi_image_picker:
    git:
      url: git://github.com/Sh1d0w/multi_image_picker.git
```

### [logger](https://pub.dev/packages/logger)

ログ周りで重宝するが、もっといいのないかな...
UI部分の[logger_flutter](https://pub.dev/packages/logger_flutter)は開発が止まっているので使えるものを利用する

```pubspec.yaml
  logger: ^1.0.0
  logger_flutter:
    git:
      url: https://github.com/etq-labs/logger_flutter
```

### [provider](https://pub.dev/packages/provider)

Widget間でのデータの共有に利用している。とにかく難しい...

### [Flutter Material Theme editor](https://github.com/rxlabz/panache)

アプリのテーマエディターとして有能そうなのですが、開発時点ではクリック数回でフリーズするので利用できなかった。
かなり良さげなので今後に期待。

### [Flutter Gallery](https://gallery.flutter.dev/#/)

基本的なUIが揃ってるので、ほぼコピペから開発できます。

### [Flutter Delivery Template](https://code.market/product/foodie-flutter-food-delivery-ui-kit/)

デザインの参考にしようとしたが、全くの別物に...ただ、テンプレートがおしゃれ

### [flutter_sliding_tutorial](https://pub.dev/packages/flutter_sliding_tutorial)

おしゃれなチュートリアル画面が簡単にできました。

![tutorial3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26833/f4034824-03d3-35b8-6865-a095c9798713.gif)


ハマったところ
-----------

### iOSリリースビルド時のエラー

開発時点でiOSのビルドをすると下記のようなエラーが表示されました。

```
       Undefined symbols for architecture armv7:
         "_OBJC_CLASS_$_FIRComponent", referenced from:
             objc-class-ref in FIRExperimentController.o
         "_OBJC_CLASS_$_FIRComponentType", referenced from:
             objc-class-ref in FIRExperimentController.o
         "_OBJC_CLASS_$_FIRDependency", referenced from:
             objc-class-ref in FIRExperimentController.o
         "_FIRLogDebug", referenced from:
             -[ABTConditionalUserPropertyController clearExperiment:variantID:withOrigin:payload:events:] in ABTConditionalUserPropertyController.o
             -[ABTConditionalUserPropertyController setExperimentWithOrigin:payload:events:policy:] in ABTConditionalUserPropertyController.o
         "_OBJC_CLASS_$_FIRApp", referenced from:
             objc-class-ref in FIRExperimentController.o
         "_FIRLogError", referenced from:
             _ABTDeserializeExperimentPayload in FIRExperimentController.o
         "_FIRLogInfo", referenced from:
             -[ABTConditionalUserPropertyController setExperimentWithOrigin:payload:events:policy:] in ABTConditionalUserPropertyController.o
             _ABTExperimentsToSetFromPayloads in FIRExperimentController.o
             _ABTExperimentsToClearFromPayloads in FIRExperimentController.o
             -[FIRExperimentController updateExperimentConditionalUserPropertiesWithServiceOrigin:events:policy:lastStartTime:payloads:completionHandler:] in FIRExperimentController.o
             -[FIRExperimentController latestExperimentStartTimestampBetweenTimestamp:andPayloads:] in FIRExperimentController.o
       ld: symbol(s) not found for architecture armv7
       clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

FirebaseCoreがうまくいっていないらしいが、armv7は今回不要だと思ったので下記のように`arm64`のみに修正

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26833/4d71c701-0b19-a75b-03e9-57da641b301b.png)

スクリーンショットの作成
----

[APPLAUNCHPAD](https://theapplaunchpad.com/)がよさそうでしたが、無料だと制限が厳しそうな印象でした。
普段はSketchを利用しているので[SketchToAppStore](https://github.com/bmull/SketchToAppStore)を元にスクリーンショットを作成しました。
他にも[shots](https://github.com/jtholloran/shots)もいいかもしれません。

カラーバランスは[materialui](https://www.materialui.co/flatuicolors)を参考にしました。

LPの作成
----

bootstrapを利用したフリーテーマ[new-age](https://github.com/startbootstrap/startbootstrap-new-age)を元に作成しました。

申請周り
----

Apple審査時の苦労話は下記にまとめました。

https://ateliee.com/archives/3834

完成品
----

とりあえず、一通りの機能もできたのでリリースしてみた。

期間としては開発1週間 + 申請周り等1週間程度でとても簡単にできました。(と、実は申請周りで手間取り2週間かかっている)

もしよかったら利用してみてください。

![Artboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26833/1c6c0c77-ebbd-2248-b58c-921d9175e4d2.png)

https://petective.ateliee.com/