はじめに
こんにちは!求人メディアGreenのエンジニアをしている@risafj です。
Greenでは、2021年2月にFlutterで開発されたAndroidアプリがリリースされた後、6月にiOSアプリも従来のSwift版からFlutter版に移行しました。私はその6月頃からアプリ開発に参画し、以来開発・運用を担当しています。今回は、その1年間でチームで行ってきたことを書いてみようと思います。
Flutterの人気は高まっているためネット上に記事がたくさんありますが、「長期に渡ってFlutterアプリをメンテするのってどんな感じ?趣味や個人開発ではなく、中規模サービスでFlutterを使うのってどう?」といった情報は意外に少なそうだったので、興味があればぜひご一読ください!
(ちなみに、現在のGreen開発チームとして日常的にFlutterで開発するのは私含めて2人で、あとは施策ベースで時折ほかのメンバーが実装に入ります。)
1年間で行ったこと
印象的だった出来事を挙げてみます。
※主に運用系のタスクを挙げていますが、事業として必要な機能開発や不具合修正も通年で行ってきました。
Flutter v2 へのアップデート
2021年にFlutter界で起きた最大の変更といえば、null safetyだったのではないでしょうか。
2021年3月のFlutter v2のリリース(正確にはDart 2.12のリリース)によりnull safetyが導入されました。 Greenでは、各ライブラリの対応を待ったうえで、8月頃にv1.22.4 → v2.2.3のアップデートを行いました。 当初は、アプリ担当の2人総出で何日も戦うことになるとは思っていませんでした…!
具体的に行った変更は、大きく分けて以下の通りでした。
関連ライブラリのバージョンアップデート
Flutterをアップデートしたのに伴い、その他のライブラリもnull safetyに対応したバージョンにアップデートする必要があったため、pubspec.yaml
の全てのライブラリが一斉にアップデートされました。
なかにはbreaking changesが紛れているものもあり、特にHTTPライブラリのdioは詳細なchangelogやupgrade guideがない中で、なんとか問題を特定してGitHub Issueを漁って修正した記憶があります。
Null safety対応
各APIのリクエストパラメータやレスポンスなどを一個一個見直していくので、膨大なコードの変更量になりました。
またGreenは性質上、nullableなデータが多いです。Null safety導入前は、final String jobName
などと変数を定義した上で、使用時に if (jobName != null) {}
のようにnullチェックをしていました。v2では、 jobName
がnullになり得る場合は final String? jobName
のように変更する必要があります。最初はnullableではないと思っていたデータについて後でnullableなエッジケースが見つかって再修正するなど、色々と手戻りがありました。
ビルドコマンドの変更
詳細は割愛しますが、ビルド時に --dart-define
を使ってビルドの種類(本番用、Staging用など)や環境変数を指定していたのがそのままでは使えなくなり、修正方法の調査に苦戦を強いられました。
今後もFlutterのメジャーアップデートには時間と覚悟が必要かもしれないと感じています…!
ちなみに、ローカル開発環境でのFlutterバージョン管理にはFVMがおすすめです。アップデート作業中に差し込みで他の開発が入っても、簡単にFlutterバージョンを切り替えられます。
GitHub Actionsを使ったCI/CDの整備
GitHub Actionsを使ってPull RequestのLintを行う設定を追加したほか、ポチッとボタンを押したらApp Store・Google Play Store・Firebase App Distributionへのデプロイを行えるようにしました。
App Storeへのデプロイに関しては内部的にFastlaneを使っています。BitriseやCodemagicといったツールも検討しましたが、アトラエでは使用していなかったため、導入のコストなども考えて最終的にGitHub Actionsを選択しました。
デプロイのGitHub Actionsを載せると説明も長くなってしまうので、Lintの設定だけご参考までに載せておきます。
name: Lint on: pull_request: types: [opened, synchronize] jobs: lint: runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: "Checkout 🛎️" uses: actions/checkout@v2 - name: "Flutter setting" uses: subosito/flutter-action@v1 with: channel: stable flutter-version: "2.5.3" - name: "Flutter pub get 👽" run: flutter pub get - name: "Flutter analyze ✅" run: flutter analyze
Firebase Crashlyticsによるエラー検知
不具合を素早く検知して修正するため、Firebase Crashlyticsを導入しました。ユーザが使っているアプリがクラッシュすると、次回アプリを起動した際にログを送ってくれる仕組みになっています。
「どのリリース以来、どんなエラーが何件発生しているのか」といった情報を教えてくれるため、とても重宝しています。今は主にリリース後などに定期的に確認して、気になるクラッシュログが上がってきたら調査するような運用にしています。
たまにStack traceを見てもエラーの発生条件が分からず、再現や修正ができない場合があるのが玉に瑕で、もっと詳しい情報を取得できないか検討したいところです。
Integration Testの導入
私事ですが、自分は今回ネイティブアプリ担当になるまではウェブ開発しかしていなかったので、アプリの「バグをリリースしてしまった場合、審査も含めて最低1日間は修正を出せない」というのが慣れるまでストレスでした。
リリース前に変更箇所や主要な機能を中心に動作確認していましたが、iOSとAndroidの両方で毎回手動テストするのはかなり時間がかかるため、Flutter標準のintegration_testを使って自動テストを導入しました。 現在は、アカウント登録・ログイン・求人の検索・応募・メッセージ送信など、主要な機能については自動テストが実装されています。
まだ完璧に運用できているとはいえず、一旦はローカル開発環境で実行して確認していますが、本当はPull Requestを上げたらテストが実行されたり、デプロイ前に実行したりを実現したいと考えています!
感想
GreenでFlutterを採用したのは英断だったと思っています!
Flutterの開発体験が良いなどの話はさておき、なんといってもiOS・Androidアプリの両方の運用を主に2人で回せているのは、別々に開発するよりかなり開発コストが抑えられているはずです。また、単一コードベースということはロジック部分は同じなので、プラットフォーム間で仕様がずれる恐れがほとんどないのも良いです。
ぼやきとしては、成熟したフレームワークに比べれば絶対的な正解がない場合が多いと感じることがあります。例えば、状態管理に関するドキュメントには10個以上のライブラリが列挙してあり、公式に推奨されている方法はありません。
また当然かもしれませんが、Flutterで開発するとはいえ、iOSやAndroid開発のことが分からないと苦戦する場面はあるなと感じています。例えば、iOSで画像付きのプッシュ通知を送信する実装を行っていたときは、「service extensionって何ぞや???」と思いながらXCodeでNotification Service Extensionを追加して、よく分かっていないためトラブルシューティングも大変だった思い出があります。
これからやりたいこと
色々ありますが、運用系のタスクでいくつか挙げるとこんな感じです。 今後も事業に必要な機能開発を進めるだけでなく、運用タスクや開発者体験の改善も行っていけたらと思っています!
- テストの拡充・運用方法の改善
- 継続的なFlutterのアップデート
- その他パッケージアップデートの定期実行・自動化
- パフォーマンスチューニング
さいごに
以上、Flutterアプリの開発・運用の1年間でした。イメージが湧きましたら幸いです。
アトラエでは一緒に働く仲間を募集しています。少しでもご興味ある方は是非スライドと採用ページもご覧ください!