👰‍♀️

Flutterで結婚式の余興アプリをつくってみた

2021/08/29に公開

私事ですが2021年8月21日に結婚式を挙げ、披露宴の余興で オールスター感謝祭 風の会場参加型クイズを行いました。Flutter(×Firebase)でアプリケーション全体を主な開発はトータル30h ほどで済みまた、不確実な要件の中開発を進めることができ、「改めて Flutter の生産性が高い。.!」と感じたのでその内容を記します。

はじめに

まず、サービスを作る上で最低限のターゲットの把握ですが、列席者の年代は親族を中心に上は70代、下は20代と幅広い層が参加する形だったため、老若男女に素直に受け入れられるコンテンツである必要がありました。余興の時間も15min と限られており、限られた時間の中で丁寧な説明がなくともスムーズにゲームに参加できるものとして考えた結果、「オールスター感謝祭」をオマージュすることを決めました。画面はもちろん、音楽なども積極的に寄せていくことで参加のハードルを極力下げるよう意識しました。
また、一度披露宴をあげた経験があるとわかると思いますが、披露宴では新郎新婦の触りの紹介はあるものの詳細に踏み込んだ紹介どんな列席者が参列されているかについての情報はあまり提供されません。今回のクイズを通して、互いの列席者の紹介を兼ねる良い機会になると思い、この企画にしました。
余興なので皆が楽しめるコンテンツにできないかなと考えていた矢先に、Twitter で流れてきた Google reCAPTCHAのパロディ で着想したのも企画の背景にあります。

実際にできたもの

画面例

残念ながら当日の映像を撮っていなかったのですが(致命的ミス…!)ざっとこんなイメージです。オールスター感謝祭オマージュのため 「ABCD」ボタン素材も探して 寄せてみましたが、テレビ系コンテンツの素材は一般受けするように? 良い意味での前時代的なダサさを感じ、新たな発見でした。

主な要件です。

- 各自のQRコードをスマートフォンで読みとりクイズに参加
- スクリーンに4択問題が表示(問題は全部で9問)
- 回答時間は10秒
- 回答終了後には正解者の中で回答時間の最も遅かった人は不正解
- 最終集計では、全設問合計の「正答数が多いx回答時間が早い」人をソートして表示する

進行もオールスター感謝祭とほぼ同じ形で進めました。

3つのアプリケーション

この余興システムは3つのアプリケーションから構成されています。

  • 会場のスクリーンに表示するアプリ
  • 設問切り替えなどを行う汎用コントローラ
  • 列席者が回答するアプリ(uid 管理)

とくにコントローラについては、普段自分が操作する分には PC 操作で済ませれば良いものの、当日は新郎として一切のアプリケーション操作はできず余興の運営チーム(以下、余興メンバー)に操作のすべてを任せる形になるため、簡単なインタフェースの実現を意識しました(余興メンバーは学校の先生などエンジニアリングには一切無縁の方々でした)。
※これに限らず、当日自分が一切何もできない前提での準備と、どんなに小さい機能でもボタンを用意したりなど細かな修正が発生しました。

ディレクトリ構成は下記の通りです。

- /packages
    - quizstar # 会場スクリーンに常に映し出すアプリ
    - quizstar_controller # 余興メンバーが操作する設問切り替えなどのコントローラ
    - quizstar_app # 列席者ご自身のスマートフォンで開く回答用アプリ - Flutter Web
    - quizstar_kit # 上記3パッケージの共通Modelやcomponentをまとめたもの

列席者操作をミニマムにする

親族はじめ年配の方も多く、スマートフォン操作に慣れていない方が1/3程度占めていたため、一切入力操作なし(回答のボタンを押すだけ)の最小限のインタフェースに留めました。
操作は下記2ステップのみであとは勝手に画面が切り替わる仕様となっております。

  1. QR コードを読み込む(席札に列席者ごとの QR コードが貼ってある)
  2. 回答の ABCD どれかのボタンを押す

また、クイズの最初には会場が賑わう練習問題を用意し、緊張感の緩和とともに操作感の不安を払拭するようにしました。
※1。に関しては1度のみの操作ですので、実質回答ボタンを押下するだけです。

ステータスに応じてすべてのアプリを制御

過去のこういった取り組みを調べると、インターネット環境ブラウザバックの問題 が影響して、当日失敗してしまった事例を目にしました。当日の会場は地上70階とインターネット環境に不安はあったものの、事前調査や準備である程度払拭するよう努め(基地局を置いたりはさすがにできないので、当日起こる事故は防ぎようがない)、ブラウザバックに関しては画面遷移を一切持たせずサーバの状態に応じてリアルタイムに表示画面を切り替える形にして事故になり得る可能性を1つずつ潰しました。

enum QuestionStatus {
  /// 問題文
  questionShow,

  /// 回答中
  answering,

  /// 回答終了
  answerTimeUp,

  /// 人数カウント
  answerCheck,

  /// 回答表示
  answerShow,

  /// 回答者ソート表示
  answerOrder,
}

疑似uidによるクエリパラメータの付与

回答数だけではなく「誰が何秒で回答したか、正解したのか」のユーザー判別したかったため、あらかじめ Firestore に格納しておいたデータに対してユニークな documentID で呼び出す形にしました(Firebase Authentication は使いませんでした)。具体的にはクエリパラメータを付与した QR コードを列席者分用意しておきました。

https://quizstar.web.app/#/?uid=xxxxxxxxxxxxxxxxxxxx

この URL 読み込み時にクエリパラメータ uid を取得できるようにするため、MaterialApp の onGenerateRoute を下記のように書きました。RouteSeetings クラスの name フィールドにパラメータ情報が格納されているので、適宜パースして取り出します。

onGenerateRoute: (settings) {
    // `onGenerateRoute`でqueryParametersを取得することでブラウザリロード時もケア
    final uri = Uri.parse(settings.name!);
    final queryParameters = uri.queryParameters;
    return MaterialPageRoute<void>(
        builder: (context) => RoutePage(
        // デバッグ時はデミーのuidで開発する
        uid: kDebugMode ? 'xxxxxxxxxxxxxxxxxxxx' : queryParameters['uid'],
        ),
    );
},

onGenerateRoute に記載することでブラウザリロード操作した場合でも、クエリパラメータが吹き飛ばずちゃんと取得してくれます。
ちなみに全列席者分の QR コードの生成ですが、Firestore の該当ドキュメントを適当に csv エクスポートした後、node-qrcode を使うことで一瞬にして全員分生成できました。

まとめ

当日は、正解者の一覧画面での ListView を使った自動スクロールが稀に上手く機能しないことがありましたが(テスト不足)、その他の部分については余興メンバーの協力もあり無事時間内にすべてのコンテンツをやり遂げることができ、会場の反応も上々でした。回答率はほぼ100%に近く、とくにスマートフォン操作に慣れていない年配の親族席の方々がすべて回答できていたのが良かったです。

不確実な要件のまま3つのアプリをまとめて開発

軒並みな感想で恐縮ですが、プライベートの時間が限られている中で3つのアプリを同時に開発でき、本番間に合わせられたのはまさに Flutter の真骨頂だと思いました。とくに、会場や音響の仕様などが不確実な状態で、開発を進めなければならない今回のケースではとくに Flutter(クロスプラットフォーム)の強さが再認識できました。iOS/Android/Web のどのプラットフォームを使うかわからない状態でも、とりあえず Flutter で開発を着手し、デプロイ時に柔軟に切り替えられる安心感は大きかったです。

Firestoreのリアルタイムリスナーの恩恵

過去に大学の謝恩会で、集計結果をリアルタイムで表示するアプリケーションをつくっていた方がおりましたが、ビジュアライズ部分を openFrameworks + WebSocket(データ受信)で作っていたらしく、openFrameworks はともかく WebSocket の知見が薄い私にとっては Firestore の恩恵が大きかったです。リアルタイムリスナーの機能を使い stream を監視することで、ステータスの変更や設問の切り替え時には即座にアプリケーションへ変更が届き、ほぼノーコストで画面を制御できるのは非常に楽でした。

【Firebase+Vue.js】大学の謝恩会の余興でリアルタイムアンケート・クイズイベントを企画した話

プロジェクタ用のビューは途中で画像や動画を挟む演出のためopenFrameworksで製作したようです。Firebase Realtime DatabaseにアクセスするのがコストだったそうなのでWebSocketで送信することにしました。

Flutter Webで絵文字が表示されない

列席者ごとの QR コードが準備できていることもあり、列席者一人ひとりへのウェルカムメッセージもオンラインで完結するようにしました。その祭、Flutter Web にて letterSpacing プロパティでデフォルト値(0)以外を指定すると絵文字が正常に表示されない問題に直面しました。ざっと調べてみてもissue があがっている様子はなく、要件的には及第点だったので未指定にして済ませました。
https://twitter.com/h_tsuruo/status/1423527871692218369?s=20

参考

Discussion