この記事では 23 新卒、24 卒内定者が開発に参加した『 日比谷音楽祭公式おさんぽアプリ 2023 』 の開発の裏側をクライアント編、サーバ編の 2 回に分けてご紹介します。 今回はクライアント編をお届けします。
クライアントチームを代表しまして、24 卒内定者の渡邊が本稿の執筆を担当します。
この記事の概要
- クライアントチームの技術的挑戦
- アプリをリリースするまでの道のり
日比谷音楽祭 2023 とおさんぽアプリ
『日比谷音楽祭公式おさんぽアプリ 2023』 (以下、おさんぽアプリ) は 2023 年 6 月 2~4 日の 3 日間で開催された 「 日比谷音楽祭 」のために開発されたアプリです。 日比谷音楽祭は親子孫3世代、誰もが気持ちのよい空間と、トップアーティストのライブやさまざまな質の高い音楽体験を、無料で楽しめることを目的としたフリーでボーダーレスな音楽イベントです。
参加者が音楽祭を楽しめるように、おさんぽアプリでは以下のような機能が提供されました。
- 事前抽選で取得したチケットをアプリ上で登録し、入場に使用する機能
- 当日抽選がある公演の抽選に応募する機能
- イベントに関する情報の提供
- GPS を使用して公園内のマップ上に現在地を表示する機能
- 公園内に設置した QR コードを使用したウォークラリー機能
クライアントチームについて
おさんぽアプリの開発においてクライアントチームはモバイルアプリの開発を担当しました。
おさんぽアプリの開発は数年単位で続く事業ではなく、数ヶ月で企画からリリースまでを行うプロジェクトです。そのため、様々なチャレンジができる場でもあります。そこで、DeNA としては日比谷音楽祭の成功に加えて、開発の過程でどのような取り組み・チャレンジ・学習ができたかも重要視しています。
今年度のアプリ実装では、昨年度に実装された資産を活用可能な部分が多かったため、クライアントチームでは技術的な挑戦を多く行いました。昨年度も開発に参加した先輩方を中心に、昨年の反省を踏まえて導入した方が良い技術や使ってみたい技術を採用しました。特に、ウォークラリー機能の実装や自動化、ライブラリのアップデートに関する挑戦を多く行いました。
チームには、23新卒、24卒内定者に加えて、実装のサポートをしてくださるエンジニアリングサポーターが参加してくださいました。
技術的挑戦
ここからはクライアントチームの技術的挑戦についてご紹介します。
ウォークラリー機能の実装
例年のおさんぽアプリでは、会場内を巡りながら楽しむことができるウォークラリー機能が提供されてきました。 そこで、今年のウォークラリーでは、スタンプを集めて景品と交換できる機能を新しく開発しました。 これは、会場内に設置されたQRコードを読み取るとスタンプがもらえる、言わばスタンプラリーのようなものです。
実装では、外部のカメラからQRコードを読み込んだときに、アプリを起動する仕組みとして Firebase Dynamic Links を導入しました。
詳しい使い方を知りたい方はこちらを参考にしてみてください。
自動化による開発効率の向上
プロジェクトを通して、煩わしい作業の多くが自動化されました。 その中でも、特にお伝えしたい内容をピックアップします。
ビルドの自動化
release ビルドからデプロイまでの作業は、環境・OSに合わせて複数回行わなくてはならないため、とても工数がかかります。 今年の日比谷音楽祭アプリでは、Staging/QA/Production の各環境に対して iOS/Android の両方の release ビルドが必要でした。
昨年は release ビルドからデプロイまでを手動でしていて非効率でしたので、今年から CI/CD ツールの Bitrise を使用して一部を自動化しました。 具体的には、各環境からビルド用のブランチを切って Pull Request を作成すると、ビルドしてできたバイナリを DeployGate (開発中のアプリを端末に配布できるサービス)にアップロードするところまで自動で行うことができます。
これだけでも十分便利なのですが、Pull Request の作成をするあたりで、面倒な手作業がまだ残っていました。アプリのバージョン管理と Git 周りの shell 操作です。
まずアプリのバージョン管理について、最初は環境ごとに分けて管理をする形になっていました。 これだと、ソースコードの状態が全く同じでも環境によってバージョンが異なる状況が発生してしまいます。 これは、管理がしづらく人為的なミスにつながりやすいです。 そこで、バージョンは Development 環境で一括して管理する形に変更しました。 バージョン管理には Git tag を用いて、タグとして commit に紐づけるようにしました。
次に shell 操作について、チームの誰でも作業が簡単にできるように、以下のような対話的なシステムを作りました。
please enter env [qa/stg/prod]:
qa
ENV: qa
PR_BRANCH: qa/20230725
≈≈≈≈≈≈≈≈≈
prev_version: 1.6.0+28
MAJOR.MINOR.PATCH+BUILD
MAJORバージョンを上げますか?(リリースで0->1) (y/N): N
MINORバージョンを上げますか? (機能追加、大きなUI変化) (y/N): y
developのHEADのタグを1.7.0+29に設定します. 良いですか? (y/N): y
≈≈≈≈≈≈≈≈≈
new_version: 1.7.0+29
pubspec.yaml のversionを1.7.0+29 (qa/20230725) に上げます. qa/20230725 を作成してコミットしていいですか? (y/N): y
≈≈≈≈≈≈≈≈≈
===PATCH_NOTE===
#XXX YYYの修正
#XXX ZZZの修正
origin/develop を qa/20230725 に取り込んで, pubspec.yamlをコミットしました. pushしてプルリクをなげていいですか? (y/N): y
≈≈≈≈≈≈≈≈≈
これは、Pull Request を作成するところまでを自動でやってくれるのですが、なんと変更差分となる Commit のメッセージを整理して、Pull Request の Comment に記載してくれるのです。 これで、パッチノートを作成するために Commit を目視で辿る手間が省けました。
このスクリプトは、作業が煩わしいと感じたインターンの1人が自主的に作ったものです。 このようなものをサクッと作ってしまうのがすごいです。
このスクリプトのおかげで、はじめて release ビルドをする人でも迷いなく実行できるようになりました。
ディープリンクURLの自動生成
Firebase Dynamic Links では、ディープリンクURLを生成する際に、各 OS のストア情報を設定する必要があります。 Android は SHA-1/SHA-256、iOS は Apple Store ID です。
つまり、Store 情報が分かり次第、ディープリンクURLの再生成が必要となります。 当初、ディープリンクURLは Firebase Console 上で1つ1つ手作業で作成していく必要がありました。 ただし、10種類のディープリンクURLを4環境分で合計40個作成するのは、ひどく煩わしい作業です。
そこで、また1人のエンジニアが自動ですべてのディープリンクURLを作成するスクリプトを作り、効率的にQRコードの再生成をすることができるようになりました。
Riverpod 2 の導入
DBから取得したデータをキャッシュして操作したり、ルーティングなどアプリ内の状態を管理するために、 Riverpod (主に状態管理の用途で用いられる Flutter ライブラリ)を使用しました。 Riverpod は昨年も使用されていたので、昨年から仕様が変更されていない部分では昨年の実装を再利用していました。
昨年のプロジェクトの終了後に
Riverpod 2
がリリースされてさらに多機能となりました。
特に注目すべきこととして @riverpod
アノテーションによる Provider の作成と (Async) NotifierProvider
が新たに使用できるようになりました。
@riverpod
アノテーションはより少ないコードで Provider の作成ができるものです。
(Async) NotifierProvider
は状態操作が可能な StateNotifierProvider
などの Provider に取って代わるものです。
Provider とエラーハンドリング周りの開発体験が上がることが利点です。
Riverpod 2 の詳しい説明はこちらのサイトを参考にしてみてください。
クライアントチームでは、最初は StateNotifierProvider
を使用した従来の記法を用いていましたが、途中で Riverpod 2 を導入するかどうか、チーム全体で議論しました。
メリットは Provider 周りのコードの記述量が少なくなることや (Async) NotifierProvider
が使えることです。
デメリットは Provider 周りの実装の統一感が無くなることや Riverpod のバージョンアップデートが必要になること、参考にできる文献が少ないことでした。
議論の結果、新しく実装する部分に関しては Riverpod 2 を導入していくことになりました。 理由としては、バージョンアップデートの際に、破壊的な変更がほとんど無いため、技術的な挑戦の一環としてチームで決断しました。
Riverpod 2 を導入した結果としては、Provider 周りの開発体験が上がりましたし、新しい記法をベースとして状態管理の手法を学ぶことができたので、とても良かったとチームで振り返りました。
この一連の議論と決断を通して、24 卒メンバーの持つ責任感はより増したように思います。 使用するライブラリなど、プロジェクト初期に行われる多くの選択は 23 卒メンバーを中心として行われてきました。 それまでは、きめられた方針についていくだけだった 24 卒ですが、このような議論を通してより主体的に行動できるようになったと感じます。
Riverpod を多用しない
Riverpod の使い方に慣れた副作用として、何もかも Provider で管理しようとする傾向がチーム内にありました。 特に、ある一画面でしか使わない状態をも Provider で管理しようとしているケースがありました。
しかし、Provider はどこからでもアクセスできてしまいます。 これは、期待しない状態の変化が発生したり、それを管理するコストが生まれてしまいます。 特に、多くの人数でコードを書く場合には危険です。
また、プロバイダの本来の目的は、直接の親子関係にない Widget 間で状態を共有することです。 内部で状態管理すべきところを Provider で管理すると、状態が破棄されるタイミングや状態が変更されうる場所の管理など、不必要なコストが発生してしまいます。
ですので、特定の一画面でしか使用しない状態に関しては、StatefulWidget
を使用して、内部で状態管理をするべきだとチームで共通認識をとりました。
エンジニアリングサポーターがこの辺は指摘してくださったので、とても助かりました。
苦戦したデザインとアニメーション
デザインはデザイナーに Figma で作成してもらいました。 概ね Figma の通りに実装できましたが、中でも苦戦したデザインを何個かご紹介します。
1. チケット
プレイガイドで当選したチケットを登録、または当日抽選に応募して当選した場合に表示されるチケットのデザインです。
チケットの使用後には、点線に沿って下部が切り取られるようなデザインになっています。 これを実現するために、点線の上部と下部を別々の Widget として実装しました。
特に、窪みのある部分は使えそうなライブラリが見つからなかったので、独自に実装する必要がありました。 そのため、 Path を自前で定義することで描画することができました。
2. Home 画面のプログラム一覧
Home 画面のプログラム一覧では、DB から取得したプログラムを開催日ごとに表示します。 縦方向にスクロールをすると各プログラムを順に確認することができ、横方向のタブ切り替えで日にちを選ぶことができます。
この動作を実現するには次のような制約を乗り越える必要がありました。
- Home 画面全体のスクロールとプログラム一覧部分のスクロールは分けない
- プログラム一覧の縦幅がプログラム数に依存して変わる
- タブの切り替え時に、スライドイン・スライドアウトのアニメーションを表示する
まずは Flutter の標準ライブラリをそのまま使用して実装を試みましたが、できませんでした。 プログラムを縦方向に表示するには ListView を、タブ切り替えは TabBarView を用いることで、実装できそうに見えます。 ただし、スクロール幅が可変なものに対して、アニメーションを伴うタブ切り替えをするのは、この2つのライブラリだけでは不十分でした。
そこで、TabBarView
を拡張的に自前実装することで解決することができました。
特に、アニメーションは切り替えられるタブのすべての組み合わせについて自作しました。
アニメーションの実装は複雑で、思い通りのものを表現するのは工数のかかる作業でした。 この部分の実装を担当したエンジニアは、デザイナーと落とし所に関してコミュニケーションを取りながら、粘り強く取り組んでいました。
単体テストの導入
昨年のおさんぽアプリ開発の反省として、今年からテストを導入することになりました。 昨年は、テストを書いていないことにより、QA が大変だったようです。
導入するテストについては、ロジック周りに限定して単体テストのみを書くことにしました。 チームとして Flutter でテストを書いた経験が少ないため、キャッチアップコストの兼ね合いなどから、単体テストのみとしました。
導入した結果としては、ロジック部分のバグの多くは防げていたので、かなり効果はあったと思います。 また、Bitrise と組み合わせることで、コードレビューの精度と効率が上がりました。
ただし、テストの書き方の学習コストは想像以上に高かったです。
特に、Riverpod 周りのテストです。
Riverpod の Provider に関するテストでは、依存している Provider を注入(DI)できるので、実装しやすいです。
しかし、Riverpod 2 を導入したため、StateNotifierProvider
と (Async) NotifierProvider
で違う書き方を学ぶ必要がありました。
そのため、実装はかなり遅れをとってしまいました。
また、画面遷移周りに重大なバグを抱えたまま、世にアプリをリリースしてしまいました。 これについては後ほど詳しく説明します。
これは、ロジック周りの単体テストに絞ってしまったせいで、画面遷移周りに生じたデグレを検知できなかったためです。 正直、今年は単体テストだけで手一杯でしたが、このような問題はテストで防げる部分ですので、来年への反省としました。
リリースまでの道のり
今年のおさんぽアプリは5月下旬にリリースしました。 23卒を中心にプロジェクトがローンチされて、2月頃には24卒が参加しました。 スケジュール的にかなりギリギリでしたが、エンジニアリングサポーターの強力な支援もあり、なんとかリリースまでたどり着けました。 その軌跡を振り返りたいと思います。
24卒は全員が Flutter 未経験
クライアントチームに参加した24卒は、Flutter を使って開発をした経験がほとんどありませんでした。 React, Ruby on Rails, Unity など、得意な技術領域は多彩でした。
対して、23卒は昨年の開発にも参加した Flutter 経験者が複数いました。 24卒は2月に参加してからキャッチアップをしつつ、23卒の方にサポートしていただいたり、ペアプロをしたりしてタスクを消化していきました。
スクラムに関しても、この頃は積極的に取り組んでいました。 プランニングポーカーをしてストーリーポイント(SP)の見積もりをしたり、レトロスペクティブでポイントを参考に Next Aciton を決めたりしました。
自由で楽しいインターン
オンボーディングはチーム全体で楽しくやりました。 午前はみんなで日比谷公園の現地調査をして、お昼は焼肉ランチをご馳走になり、午後はオフィスの大きな会議室で作業をしました。
大阪や愛知のような遠方にお住まいの方も、希望すれば朝早く起きて渋谷のオフィスに出社することができました。 こういうときは、みんなで予定を合わせて近くの席で作業をしたり、ランチをしたり、終業後にビールを飲んだりしました。
また、 TechCon がこの時期に開催されたので、インターンもオフィスに集まって参加しました。 当日スタッフをやっているメンバーもいました。
4月から23卒がいない
23卒は研修のため4月からプロジェクトを離れます。 頼れる先輩エンジニアが一気にいなくなり、Flutter 経験の浅い24卒だけが残りました。
それと同時に、QA とデザインの適応を並行して始める必要がありました。 また、チケット機能や Map 機能については、ロジックの実装がまだ完了していませんでした。 これに関しては、技術的挑戦をした分、スケジュール的に厳しくなってしまいました。 特に、残タスクのSPとこれまでのスプリントごとのSP消化率を比較して、開発が間に合わない可能性が濃厚でした。 残り2ヶ月で本当に終わらせられるのか、と正直思いました。
幸いこのタイミングでエンジニアリングサポーターが本格的に関わってくれることになりました。 苦戦していたチケット機能のロジックをはじめ、全体的にサポートしていただけることになりました。
QA とデザイン適応
4月上旬にデザインが概ね上がるのと同時に、ロジック周りのQAがスタートしました。 デザインをできるところから当てつつ、優先度の高いQAバグに対応していくため、作業量が一気に増えました。
デザイン、QAともにスケジュールに収まる量のタスクではなかったので、かなりの量のタスクを Won’t do にしてもらいました。 特にデザインは、一度はエンジニアチームで実装可能とお伝えしたものを、工数懸念で実装不可能とすることが多々ありました。 この辺りは、24卒で適切に見積もることができていなかったからだと言えます。
タスクについて、デザイナーやPdM、QAと連携をとりながら調整することで、5月上旬にはなんとか目処が立つところまできて、5月中にすべてを完了することができました。
Store 申請
QAやデザイン適応と並行して、Store にアプリを掲載するための申請を進めました。 作業は主にPdMと社内のストアサポートチームが連携して行いました。
初回の申請時が5月下旬と既にギリギリだったのですが、iOS, Android ともにリジェクトされてしまいました。
結果、アプリ内に表示する言葉の修正だけで済んだのですが、エンジニアリングサポーターが中心となってリジェクト回避策を講じる様子は緊張感があり、一番現場を感じました。
上記の作業により、無事審査を通すことができました。
リリース後発覚バグ
アプリはリリースできましたが、最初のリリースでは2つの大きなバグを伴って世に出してしまいました。 すぐに修正版をリリースしたので大事には至らなかったのですが、ご紹介します。
ウォークラリー機能の Routing にデグレが発生
1つ目は、ウォークラリー機能の画面遷移です。
QRコードの読み取りが成功した後に、本来であればスタンプ一覧のページに遷移するはずなのですが、Page not found
と表示されるようになってしまいました。
これは、リリース直前にQAバグ対応として Routing を少し修正したために発生したデグレで、リリースを急いでいたこともあり、リリースまでに発見ができませんでした。 アップデート版のリリース時には、より慎重にQAチームに確認してもらい、無事審査に通過したものをリリースしました。
実はこのバグに遭遇しているユーザーを当日この目で見てしまいました。
当日、私たちも会場に行ってウォークラリーをやっていたのですが、目の前でQRコードを読み取っていたユーザーさんのスマホには、真っ白な画面にポツンと Page not found
と書かれていました。
これには、一緒にウォークラリーを回っていたメンバー一同驚愕してしまいました。
Push通知でアップデートを促してもらいましたが、当然されていない人もいらっしゃるようです。
世に出す前にバグを検知することの重要さを身にしみて感じた瞬間でした。
端末に設定された文字サイズによって、UIに破壊的な影響が出る
2つ目は、端末に設定された文字のサイズがそのままアプリに反映されてしまい、UIが崩れてユーザーの操作に大きく影響してしまうことです。 特に、アプリの初回起動時に表示される Push 通知許可ページについて、端末に設定された文字のサイズが大きく、画面のサイズが小さい場合に、次に進むためのボタンが画面外に押し出されてしまいます。 この場合、先のページに進めなくなってしまうため、チケット機能などの基幹機能を使用できません。
対応としては、端末に設定された文字サイズがアプリ内に反映されないようにすることで解決しました。 アップデート版がリリースされるまでは、端末の文字のサイズを一時的に標準のものに戻してご利用いただきました。
このバグは、リリースしたアプリをたまたま文字サイズを大きく設定している人に使用してもらった際に気づきました。 QAおよびエンジニア的にも想定外で、緊急対応しました。
当日の様子
当日は、サーバーチームとPdMと一緒に会場内のアプリ障害窓口で状況を見守りました。 当日の様子はこんな感じです。
アプリ障害窓口はチケットの受付の横にあり、非常に多くの方がアプリの操作をしているのを間近で見ることができました。 作ったばかりのアプリについて、ここまで近く、たくさんのリアクションを見ることができるのはそうそうない機会です。 とても感動しました。
幸い、開催期間中を通して、クライアントチーム側は大きな問題なく終えることができました。 サーバーチームによるインフラ周りの当日対応がすごかったので、後ろから応援していました。 サーバーチームに関するお話は、詳しくはこちらをご覧ください。
おわりに
おさんぽアプリの開発を通して、非常に多くの実務的な経験をすることができました。 エンジニアチームはインターンが中心となって構成され、多くの裁量を持ちながら、PdMやデザイナー、QA、社内審査、ストアサポートなど、様々な方と関わりながら働きました。 インターンという立場はあまり意識されることなく、一エンジニアとして関わっていただけました。
正直、プロジェクト後半には、リリースが間に合わないのでは、と何度も頭をよぎりました。 ただし、エンジニアリングサポーターの強力な支援のおかげで、なんとか間に合わせることができました。 失敗ギリギリでした。
結果として、非常に多くの人が目の前でアプリを使われていて、作ったものが実際に使われていることを実感することができました。 これにはとても感動しました。
また、24卒としては、先輩である23卒の方々から多くを学び、刺激を得ることができました。 これは、24卒同士でも同様でした。 数ヶ月間、共に同じ目標に向かった仲間として、今後も切磋琢磨しながら成長していきたいと思います。
最後になりますが、このようなインターンの機会を頂けて感謝の気持ちでいっぱいです。 最高のインターンでした!
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。