👰‍♀️

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

6 min read

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

はじめに

まず、サービスを作る上で最低限のターゲットの把握ですが、列席者の年代は親族を中心に上は70代、下は20代と幅広い層が参加する形だったため、老若男女に素直に受け入れられるコンテンツである必要がありました。余興の時間も15minと限られており、限られた時間の中で丁寧な説明がなくともスムーズにゲームに参加できるものとして考えた結果、「オールスター感謝祭」をオマージュすることを決めました。画面はもちろん、音楽なども積極的に寄せていくことで参加のハードルを極力下げるよう意識しました。
また、一度披露宴をあげた経験があると分かると思いますが、披露宴では新郎新婦の触りの紹介はあるものの詳細に踏み込んだ紹介どんな列席者が参列されているかについての情報はあまり提供されません。今回のクイズを通して、互いの列席者の紹介を兼ねる良い機会になると思い、この企画にしました。
余興なので皆が楽しめるコンテンツにできないかなと考えていた矢先に、ツイッターで流れてきた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階でインターネット環境に不安はあったものの、事前調査&準備である程度払拭するよう努め(基地局を置いたりはさすがにできないので、当日起こる事故は防ぎようがない)、ブラウザバックに関しては画面遷移を一切持たせず、サーバの状態に応じてリアルタイムに表示画面を切り替える形にして事故になり得る可能性を一つずつ潰しました。

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(クロスプラットフォーム)の強さが再認識できました。ネイティブアプリか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

ログインするとコメントできます