Android - TECH PLAY - TECH PLAY

TECH PLAY

Android

イベント

マガジン

技術ブログ

    株式会社エブリーは、「前向きなきっかけを、ひとりひとりの日常にとどける。」というミッションのもと、デリッシュキッチン、retail HUB、トモニテ、MOMENTH といった BtoC・BtoB を横断するメディアやサービスを展開しています。同社では、小売事業者向けサービス retail HUB におけるピッキングサービスの新規開発にあたり、データベースとして Amazon Aurora DSQL を採用しました。本ブログでは、お客様の開発チームに伺った Aurora DSQL 採用の背景、導入の取り組み、そして導入後に得られた効果についてご紹介します。 対象システム 今回 Aurora DSQL を採用したのは、 retail HUB 事業のネットスーパーサービスにおけるピッキングサービスです。 retail HUB は、お客様が提供する小売事業者向けの DX ソリューションです。店頭サイネージによるレシピ提案、デジタルチラシ、ネットスーパーアプリの提供、店頭運営の効率化、CRM によるロイヤルカスタマー育成まで、小売事業者の販促プロセス全体をデジタル化し、業務効率化ときめ細やかな顧客アプローチを実現しています。 retail HUB 事業のネットスーパーサービスでは、店舗側が売り場から注文商品をピックアップする作業が発生しますが、従来この運用は紙のリストで行われており、商品を探すのに時間がかかることや習熟度による作業スピードのばらつきなどにより人的ミスの防止が難しく、誤ピックによる取り直しが発生するといった課題がありました。この問題を解決するため、ピッキングアプリ「retail HUB Picker」の開発が求められました。 アーキテクチャ ピッキングサービスのアーキテクチャは、ピッキングアプリ(Android アプリ)から Amazon ECS + ALB で構成された API サーバーに接続し、担当者の認証管理には Amazon Cognito を利用しています。バックエンドのデータベースとして Aurora DSQL を採用し、ピッキング対象の商品情報やピッキング状態、担当者情報の格納・参照に使用しています。 ピッキングサービスのシステムアーキテクチャは以下のとおりです。今回新規に開発したピッキングシステムは既存のネットスーパーシステムとは分離しており、注文データを連携する箇所のみで結合しています。 データベース選定の背景 今回のピッキングサービスは新規開発であり、データベースの選定にあたってはシステムの特性に合わせた検討が行われました。データベースに求められる要件は以下でした。 安定性と可用性 ピッキング作業中にシステムが停止すると店舗のピッキング業務に影響を与え、導入店舗数が増えるほどその影響範囲も大きくなるため、安定性と高い可用性が重要な要件でした。また、小売店舗は土日も稼働しているため、運営側の都合でメンテナンス時間を設定しにくいというビジネス上の課題も考慮する必要がありました。 低コストによるスモールスタート 初期フェーズではリクエスト数が限定的であり、構築・運用にあまりコストをかけたくないという要望がありました。一方で、今後サービスの成長に伴いトラフィックの増加も想定されるため、高いスケーラビリティを備えたデータベースであることが望ましいと考えていました。 運用負荷の軽減 メンテナンスウィンドウに伴う運用負荷が課題となっていました。新規サービスでは、こうした運用負荷を極力下げたいという考えがありました。 Aurora DSQL を選択した理由 お客様では当初、既存システムで利用している Amazon RDS for MySQL や Amazon Aurora MySQL の導入を想定していました。しかし、上記の要件を踏まえて検討を進めた結果、サーバーレスの分散データベースである Aurora DSQL が採用されました。その理由は以下の通りです。 安定性と可用性 — シングルリージョン構成で 99.99%(マルチリージョン構成では 99.999%)の可用性を備えたサーバーレスの分散データベースであり、インフラ管理が不要。ピッキング作業中のシステム停止リスクを低く抑えられる 低コストによるスモールスタート — 従量制の課金モデルで初期コストを抑えつつ、将来のサービス成長に伴うトラフィック増加にも自動スケールで対応できる 運用負荷の軽減 — パッチ適用がダウンタイムなしで自動的に行われるため、関係者との調整や作業工数が不要になる 加えて、Aurora DSQL が PostgreSQL との互換性を備えていたことで、社内の既存知見を活かしながら導入できる点も後押しとなりました。また、お客様の開発チームには分散データベースへの技術的な関心が高く、Aurora DSQL を通じて得られる知見が今後の技術選定の指標になるという期待も大きく、採用の決め手の一つとなりました。 Aurora DSQL とは Aurora DSQL は、AWS が提供するサーバーレスの分散 SQL データベースです。従来のデータベースのようにクエリ処理・ストレージ・トランザクション管理が密結合した構成ではなく、それぞれが独立したコンポーネントに分かれています。各コンポーネントがワークロードに応じて個別にスケールするため、特定の処理がボトルネックになりにくく、低レイテンシーで強固な一貫性を実現しています。また、PostgreSQL との互換性を備えており、既存の PostgreSQL の知見やツールを活かして開発を進めることができます。 お客様が選定の理由に挙げた以下の 3 つについて、Aurora DSQL の特性を簡単に説明します。 安定性と可用性 Aurora DSQL では、すべての書き込みトランザクションを分散トランザクションログにコミットし、コミットされたすべてのログデータを 3 つのアベイラビリティーゾーンにあるストレージレプリカに同期レプリケーションします。これにより、Aurora の Multi-AZ 構成と同等の 99.99% の可用性を実現しています。以下の図は、Aurora DSQL の可用性に関する特性を示しています。 低コストによるスモールスタート 課金はリクエスト数やデータ量に応じた従量制で、リクエスト数が少ない段階ではコストを低く抑えられます。アーキテクチャを変更することなく自動的にスケールするため、小規模な構成から始めて、トラフィックの増加に応じてシームレスにスケールすることができます。以下の図は、Aurora DSQL のコストモデルを示しています。 運用負荷の軽減 サーバーのプロビジョニングやパッチ適用、インフラのアップグレードといった管理作業が不要です。パッチ適用やセキュリティアップデートはダウンタイムなしで自動的に処理されるため、メンテナンスウィンドウの調整や計画停止が不要です。以下の図は、Aurora DSQL と従来のデータベースにおけるメンテナンスの違いを示しています。 開発時の課題 開発スケジュール 開発スケジュールは以下のとおりです。 時期 内容 2025年5月 小売事業者に対するヒアリング(事業部門+開発部門) 2025年6月 運用分析、開発要件定義、分析要件定義、システム設計 2025年7月〜8月 開発(アプリケーション、API サーバー、インフラ、分析基盤) 2025年9月上旬 社内 QA 2025年9月中旬 リリース、小売事業者にて検証・運用開始 Aurora DSQL を用いた開発にあたっては、PostgreSQL との互換性に関していくつかの課題がありました。 スキーマ変更の制約 Aurora DSQL ではカラム追加時にデフォルト値を指定する構文がサポートされていないことが開発中に判明しました。PostgreSQL では一般的に使用される構文であるため、既存の知見をそのまま適用できないケースでした。対処として、カラムのみを追加し、デフォルト値や NOT NULL 制約はソフトウェアレベルで保証する実装としました。なお、サービスリリース前であったため、最終的にはテーブルを再作成することで問題を回避しました。このように、事前のドキュメント確認だけでは把握しきれず、実際の操作を通じて発見される差分もありました。 ローカル開発環境との互換性 当初ローカル開発では PostgreSQL のコンテナを使用していましたが、 CREATE INDEX を CREATE INDEX ASYNC に変更する必要があるなど、Aurora DSQL との互換性がない部分が判明したため、ローカル環境でも Aurora DSQL を使用する方式に切り替えました。 トランザクションサイズの制限 Aurora DSQL にはトランザクションデータ件数に制限があるため、大量のデータを一括で処理する実装には工夫が必要でした。この制限を回避するため、大量のデータ投入処理においては全体を 1 トランザクションにまとめず、処理を分割する設計としました。具体的には、データ量が大きい投入処理ではトランザクションを使わずに実行し、途中でエラーが発生しても何度でもやり直せる設計としました。その上で、整合性の担保が必要な少量のデータ更新処理だけをトランザクション内で実行するようにしました。 こうした課題を一つずつ解消しながら開発を進め、ピッキングサービスは無事にローンチを迎えました。 ローンチと導入後の効果 2025 年 9 月中旬にローンチしてから数ヶ月が経過していますが、大きな問題は発生しておらず、安定した稼働を続けています。 導入後に得られた効果は以下のとおりです。 コスト削減 Aurora PostgreSQL(Provisioned)で想定していたインスタンスサイズと比較すると、コストは 90% 以上低くなっています。従量制のコストモデルにより、初期フェーズにおいて大幅なコスト削減を実現しました。 以下は、今回のワークロードを想定したコスト比較の試算です。Aurora PostgreSQL(Provisioned)を 100 とした場合の相対コストを示しています。 構成 Pattern S コスト比 Pattern M コスト比 Pattern L コスト比 Aurora PostgreSQL (Provisioned) 100% 100% 100% Aurora Serverless v2 41% 41% 66% Aurora DSQL 4% 6% 12% 安定稼働とメンテナンスフリー サービス導入後は安定して稼働しており、パフォーマンス上の問題も発生していません。メンテナンスウィンドウが不要であるため、基盤のメンテナンス作業そのものだけでなく、それに伴う小売事業者やサービス利用者との調整業務も不要となりました。既存システムでは 3 ヶ月ごとのパッチアップデートのたびに、メンテナンス時間帯の調整、影響範囲の洗い出し、小売事業者や関係部署への連携が必要でしたが、Aurora DSQL ではこうした業務コストがゼロになり、運用負荷が大きく削減されました。 スケーラビリティの確保 現時点でのリクエスト数の変動については特に大きな問題は発生していません。今後のサービス成長に伴うトラフィック増加についても、検証の結果スケーリングに問題がないことを確認しており、今後のトラフィック増加にも不安なく運用できる見通しです。 お客様の声 お客様に Aurora DSQL の導入効果について伺ったところ、次のように述べています。 スケーラビリティと安定性の要件が高いシステムにおいて Aurora DSQL を選択する利点は高いと考えています。 – 内原 章 氏 株式会社エブリー 開発本部 開発2部 部長 今後の展望 お客様では、今回のピッキングサービスでの導入実績を踏まえ、スケーラビリティの要件が高いシステムにおいて Aurora DSQL を選択肢として検討していく方針です。新規開発だけでなく、既存システムのリプレースにおいても Aurora DSQL の活用を視野に入れています。 また、Aurora DSQL のさらなる進化への期待として、PostgreSQL との互換性向上を挙げています。特に RLS(Row Level Security)のような機能がサポートされることで、より幅広いユースケースに対応しやすくなると考えています。 Aurora DSQL は、高い可用性とスケーラビリティを必要とするシステムにおいて、運用負荷とコストの低減が期待できるデータベースです。本ブログが、同様の課題を持つお客様にとって参考になれば幸いです。
はじめに こんにちは、新規事業部フロントエンドブロックの 大野純平 です。2025年度に新卒入社し、現在のチームに配属されました。チームでFlutter製モバイルアプリを開発する中で、新規プロジェクトの立ち上げを効率化するための社内テンプレートリポジトリの整備を進めています。 このテンプレートを育てる中で、 Flutter 3.44 へのアップデートに伴うCocoaPodsからSPMへの移行対応が積み残しになっていました。あわせて、このタイミングでdev / stg / prdの3環境対応も新規に追加しました(3 flavor化自体はSPM移行の必須要件ではなく、テンプレート整備の一環で同時に着手したものです)。本記事では、 CocoaPodsからSPMへの移行 と、 3 flavorを mise run setup で一括整備する仕組み を紹介します。あわせて、Flutterにおけるビルド構成ファイルを解説します。 目次 はじめに 目次 Flutterプロジェクトのビルド構成 iOS側のしくみ Android側のしくみ Dart側のしくみ 3層を貫く全体像 背景・課題 3環境の設定整合が壊れやすい 解決策の全体像 実装内容と具体的な解決策 1. CocoaPodsを撤去してSPMに移行する CocoaPodsの統合を解除する Crashlytics dSYMのパスを更新する 2. iOS:xcconfigでBuild Configurationを9パターンに整える Firebase plistをBuild Phaseで差し替える 3. Android:productFlavorsとgoogle-services.jsonをflavorごとに配置する 4. Dart:firebase_optionsをセレクタshim化する main.dartでduplicate-appを処理する 5. mise run setupで全flavorを一括生成する まとめ Flutterプロジェクトのビルド構成 Flutterアプリは iOSネイティブ層・Androidネイティブ層・Dart層 の3つが組み合わさっています。環境(flavor)の切り替えとは「3層すべてに、同じflavor用の設定を選ばせること」です。 iOS側のしくみ Xcodeは以下の単位を組み合わせてビルドを定義します。 単位 役割 Flutter デフォルト Project ( .xcodeproj ) プロジェクト全体の定義。実体は project.pbxproj Runner.xcodeproj Target ビルド成果物の単位 Runner Build Configuration ビルド設定値の集合 Debug / Profile / Release の 3 種 Scheme Run / Test / Profile / Analyze / Archive の各アクションで使う Target と Configuration を定義する起動設定 Runner 1 つ xcconfig はBuild Configurationの値をテキストで宣言するファイルです。別のxcconfigを #include で取り込んだとき、同じ変数があれば 後から読み込んだ値が優先 されます。共通値を書いたxcconfigを先に、flavor固有値を書いたxcconfigを後に重ねれば、最終的な値が組み上がります。 Info.plist の CFBundleIdentifier は $(PRODUCT_BUNDLE_IDENTIFIER) を参照しています。そのため、xcconfig側で PRODUCT_BUNDLE_IDENTIFIER を決めればBundle IDが自動的に解決されます。 iOS・macOSで外部ライブラリを使うときの依存管理ツールには、 CocoaPods と Swift Package Manager(SPM) の2つがあります。Flutterプロジェクトでは長らくCocoaPodsが標準でしたが、Flutter 3.44からiOS・macOSの両方でSPMがデフォルトに切り替わりました。両者の違いは以下のとおりです。 CocoaPods Swift Package Manager (SPM) 提供元 OSS(Ruby 製) Apple 公式 設定ファイル Podfile Package.swift (Xcode では Package Dependencies UI から編集) 成果物 Pods/ + .xcworkspace を別管理 Xcode が直接管理 ロックファイル Podfile.lock Package.resolved Flutter での状況 長年の標準 3.44 でデフォルト有効化 Android側のしくみ AndroidのビルドはGradleが android/app/build.gradle.kts を読んで実行します。 概念 役割 buildTypes ビルド種別( debug / release ) productFlavors 同じアプリのバリアント定義。 applicationId やリソースを分岐できる applicationId / applicationIdSuffix アプリの一意な識別子。iOS の Bundle ID 相当 resValue ビルド時に文字列リソースを生成する Gradle の機能 AndroidManifest.xml アプリのメタデータ宣言。 android:label でアプリ名を指定 Gradleは、flavor別のリソース( AndroidManifest.xml や res/ 配下など)を source set という仕組みでディレクトリ単位に分けます。Gradleがflavorを選んだときに src/main/ と自動マージします。なお google-services.json は厳密にはsource set mergeの対象ではありません。google-servicesプラグインが src/<flavor>/google-services.json などのパスを優先度順に探索し、1ファイルを選びます。 Dart側のしくみ Dartでflavorを識別するにはコンパイル時定数を使います。 API / オプション 役割 --dart-define-from-file=path/to/x.json .json または .env ( KEY=VALUE 形式)で書いた値をまとめて Dart に渡す flutter コマンドのオプション String.fromEnvironment('KEY') 渡された値をコンパイル時定数として参照する API。 switch の case 値にもできる firebase_options.dart flutterfire configure が生成する Firebase 設定ファイル。通常は 1 ファイル = 1 Firebase プロジェクト 3層を貫く全体像 3層を1枚にまとめると、 app_config.yaml → mise run setup → 各層のファイル群 → flutter run --flavor X という流れです。以降、iOS・Android・Dartの順に、各層内で mise run setup と flutter run --flavor X がどのファイルへつながるかを示します。 iOS層では、xcconfigチェーンが Info.plist を解決します。 project.pbxproj のRun Scriptがflavor別の GoogleService-Info.plist をコピーします。 Android層では、 build.gradle.kts がflavorディレクトリの google-services.json と AndroidManifest.xml を結びつけます。 Dart層では、 flavor/{dev,stg,prd}.json の FLAVOR が起点になります。 firebase_options.dart が FLAVOR に応じて firebase_options_{dev,stg,prd}.dart を切り替えて返すコード( セレクタ shim )になります。 flutter run --flavor dev --dart-define-from-file=flavor/dev.json を1回実行したときに、各層で何が起きるかを並べると以下のようになります。 iOS :Scheme dev → Debug-dev.xcconfig で PRODUCT_BUNDLE_IDENTIFIER が .dev 付きに解決される。Build PhaseのRun Scriptが ios/firebase/dev/ のplistをコピーする。 Android :productFlavor dev が選ばれて applicationIdSuffix=".dev" が適用される。 src/dev/google-services.json がsource setにマージされる。 Dart : flavor/dev.json の FLAVOR=dev がコンパイル時定数になる。 firebase_options.dart のセレクタshimが firebase_options_dev.dart を返す。 ここまでがFlutterプロジェクトのビルド構成の概要です。これを踏まえて、本記事で取り組んだ課題と解決策を見ていきます。 背景・課題 3環境の設定整合が壊れやすい dev / stg / prdの3環境を切り替えるには、iOS・Android・Dartの3層を整合させる必要があります。各層の対象は次のとおりです。 iOS:Bundle ID、 GoogleService-Info.plist Android: applicationId 、 google-services.json Dart:Firebase初期化オプション 3層が独立しているため設定漏れによるビルド失敗やFirebase接続不可が繰り返し発生し、原因特定にも時間がかかっていました。 解決策の全体像 以下の5つの取り組みで課題を解決しました。 # 層 取り組み 1 iOS / 基盤 CocoaPods 撤去 → SPM 移行 2 iOS / flavor xcconfig × 9 Build Configuration + flavor 別 Firebase plist 3 Android / flavor productFlavors + flavor 別 google-services.json 4 Dart firebase_options.dart のセレクタ shim 化 5 setup app_config.yaml の per-flavor 化と mise 自動化 1つのflavorを選んで flutter run すると、iOS・Android・Dartの3層でそれぞれ対応する設定が適用されます。 実装内容と具体的な解決策 1. CocoaPodsを撤去してSPMに移行する CocoaPodsはRuby製のAppleプラットフォーム向けパッケージマネージャーで、Swift Package Manager(SPM)はApple公式の代替です。Flutter 3.44では新規プロジェクトでiOS・macOSともにSPMがデフォルトで有効化され、Firebaseをはじめとする主要プラグインもSPM対応を完了しています。 pod install が不要になるため、CIのセットアップも大幅に高速化されます。 CocoaPodsの統合を解除する 既存プロジェクトでは、 Podfile をいきなり削除するのではなく、まず pod deintegrate でXcodeプロジェクトからCocoaPodsの統合を解除します。 cd app/ios pod deintegrate # project.pbxproj から Pods 統合を除去 rm Podfile Podfile.lock # Podfile 本体を削除 rm だけで済ませてはいけません。 Podfile を削除しただけでは project.pbxproj にCocoaPods統合の痕跡が残ります。たとえば [CP] Check Pods Manifest.lock などのBuild Phaseや、 Pods-Runner xcconfigの #include 参照などです。 pod deintegrate はこれらを正しく除去します。 .gitignore も忘れず更新します。 # app/ios/.gitignore -.symlinks/ -Pods/ +# SPM +.build/ SPMが管理する Package.resolved はバージョンを固定するためリポジトリにコミットします。 Crashlytics dSYMのパスを更新する dSYM(Debug Symbols)はクラッシュレポートのスタックトレースを人間が読めるシンボルに復元するためのファイルです。Crashlyticsを使っている場合、そのdSYMをアップロードするBuild PhaseスクリプトをSPM構成のパスに合わせて更新します。このRun Scriptは setup 系スクリプトが自動生成するものではなく、Crashlyticsを利用する場合はプロジェクトごとに手動で追加する手順です。 Firebase公式のApple向けガイド は現在SPM構成を前提としており、Run Scriptに以下を追加するよう記載されています。 # Build Phases > Run Script " ${BUILD_DIR % /Build/* } /SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run " CocoaPods時の ${PODS_ROOT}/FirebaseCrashlytics/run に相当します。Xcode 15以降は User Script Sandboxing がデフォルトで有効になっています。有効な場合はRun ScriptのInput Filesに以下を列挙する必要があります( Firebase公式ドキュメント )。 ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME} ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME} ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist $(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist $(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH) # Debug Dylib(DEBUG_DYLIB)が有効な場合は追加で必要 ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}.debug.dylib 2. iOS:xcconfigでBuild Configurationを9パターンに整える iOSのXcodeプロジェクトは「Build Configuration」というビルド設定の単位を持ちます。各Configurationにはxcconfigファイル(テキストでビルド設定を記述する仕組み)を1つ紐付けられます。本テンプレートは、以下の9通りのBuild Configurationを用意しています。 dev stg prd Debug Debug-dev Debug-stg Debug-prd Profile Profile-dev Profile-stg Profile-prd Release Release-dev Release-stg Release-prd これらはあらかじめ project.pbxproj にコミット済みです。あわせて、それらを参照する dev / stg / prd の3 Schemeもコミット済みです。各Schemeは、Run/Test/Analyzeで Debug-<flavor> を参照します。Profileでは Profile-<flavor> 、Archiveでは Release-<flavor> を参照します。3つのSchemeには、Flutter標準の Run Prepare Flutter Framework Script pre-actionも含めて完全な状態で用意されています。このpre-actionは xcode_backend.sh prepare を実行します。これによりSPM統合に必要な FlutterGeneratedPluginSwiftPackage が解決されます。 mise run setup はこのScheme⇔Configurationのマッピングやpre-actionには一切触れません。書き換えるのは、各Configurationに紐づくxcconfigの値(Bundle IDやアプリ名など)だけです。 Flutter/Shared.xcconfig ← 全 Configuration 共通の値 Flutter/flavor-dev.xcconfig ← dev 固有の値(BUNDLE_ID_SUFFIX など) Flutter/flavor-stg.xcconfig Flutter/flavor-prd.xcconfig Flutter/Debug-dev.xcconfig ← #include で連結(9ファイル) Flutter/Debug-stg.xcconfig ... Flutter/Release-prd.xcconfig 後ろの #include が前の値を上書きするため、共通値( Shared )→ flavor固有値( flavor-dev )の順に重ねることで最終的な値が決まります。 Debug-dev.xcconfig は3行の #include のみで構成されます。 #include "Debug.xcconfig" #include "Shared.xcconfig" #include "flavor-dev.xcconfig" これでビルド時に対応するxcconfigが PRODUCT_BUNDLE_IDENTIFIER を解決します。その値が Info.plist 経由でflavor付きBundle ID(例: jp.testapp.dev )として適用されます。 Firebase plistをBuild Phaseで差し替える flavor別の GoogleService-Info.plist を使い分けるため、Build PhaseにRun Scriptを追加します。Run Script内の ${FLAVOR} は実行直前にxcconfigの値( dev / stg / prd )に置き換えられます。これによりコピー元ディレクトリがflavorごとに切り替わり、該当するplistだけが .app バンドルに入ります。 # Build Phases > Run Script SRC = " ${SRCROOT} /firebase/ ${FLAVOR} /GoogleService-Info.plist " DST = " ${BUILT_PRODUCTS_DIR} / ${PRODUCT_NAME} .app/GoogleService-Info.plist " if [ -f " $SRC " ]; then cp -f " $SRC " " $DST " else echo " warning: $SRC not found, Firebase native init will be skipped " fi plistが存在しないflavorでもビルドが失敗しないよう、存在チェックを入れています。 firebase_project_id を空にしてFirebase設定をスキップした場合がこれに該当します。このRun Scriptは読み書き対象のパスをInput Files / Output Filesとしてあらかじめ宣言しています。そのためUser Script Sandboxingが有効な環境でも安全に動作します。 plistは以下のスロットに配置します( setup:firebase が生成)。 app/ios/firebase/ ├── dev/GoogleService-Info.plist ├── stg/GoogleService-Info.plist └── prd/GoogleService-Info.plist 3. Android:productFlavorsとgoogle-services.jsonをflavorごとに配置する Androidでは build.gradle.kts に productFlavors を追加し、flavorごとに applicationIdSuffix とアプリ名を設定します。 // app/android/app/build.gradle.kts flavorDimensions + = "env" productFlavors { create( "dev" ) { dimension = "env" applicationIdSuffix = ".dev" resValue( "string" , "app_name" , "Flutter Template App Dev" ) } create( "stg" ) { dimension = "env" applicationIdSuffix = ".stg" resValue( "string" , "app_name" , "Flutter Template App Stg" ) } create( "prd" ) { dimension = "env" resValue( "string" , "app_name" , "Flutter Template App" ) } } google-services.json はAndroid Gradleのflavorソースセット( src/<flavor>/ )に配置します。flavor用の設定ファイルはgoogle-servicesプラグインが自動で選択します。 android/app/src/ ├── dev/google-services.json # setup:firebase が生成 ├── stg/google-services.json └── prd/google-services.json 注意点が2つあります。1つ目は google-services.json の package_name です。suffix込みの最終applicationIdに合わせます(例: com.example.app.dev )。suffixなしの com.example.app だけFirebaseに登録していると、dev / stgビルド時にプラグインが例外を投げてビルドが通りません。Firebase側のアプリ登録もsuffix込みのapplicationIdで行う必要があります。 AndroidManifest.xmlで android:label="@string/app_name" のように参照します。 productFlavors の resValue がflavorごとに生成した文字列が、そのままアプリ名として使われます。 <!-- app/android/app/src/main/AndroidManifest.xml --> <application android : label = "@string/app_name" ...> 2つ目は、 strings.xml との重複です。 resValue("string", "app_name", ...) はビルド時に文字列リソースを生成します。そのため、 res/values/strings.xml に同名の app_name が定義されていると Duplicate resources エラーになります。 app_name は strings.xml には置かず、各flavorの resValue に一本化してください。 4. Dart:firebase_optionsをセレクタshim化する flutterfire configure が生成する firebase_options.dart は単一の設定しか返しません。そこでファイル名とAPIを保ったまま、 セレクタ shim に置き換えます。 まず app/flavor/dev.json に FLAVOR キーを定義し、 --dart-define-from-file で読み込みます。 { " FLAVOR ": " dev " } FLAVOR はDartコード側で String.fromEnvironment('FLAVOR') を使って、コンパイル時定数として参照できます。 // app/lib/firebase_options.dart import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'firebase_options_dev.dart' as dev; import 'firebase_options_prd.dart' as prd; import 'firebase_options_stg.dart' as stg; const _flavor = String .fromEnvironment( 'FLAVOR' ); class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { switch (_flavor) { case 'dev' : return dev.DefaultFirebaseOptions.currentPlatform; case 'stg' : return stg.DefaultFirebaseOptions.currentPlatform; case 'prd' : return prd.DefaultFirebaseOptions.currentPlatform; default : throw StateError( 'Unknown FLAVOR: "$_flavor". ' 'Run with --dart-define-from-file=flavor/<flavor>.json' , ); } } } 呼び出し側( main.dart )は DefaultFirebaseOptions.currentPlatform をそのまま使うだけで、flavorの切り替えを意識する必要がありません。 setup:firebase はこのファイルを上書きせず、本物の firebase_options_<flavor>.dart だけを再生成します。 注意:flavor指定は必須 _flavor が空( --dart-define-from-file 未指定)だとshimの default が StateError を投げます。 _initializeFirebase はこれをcatchしないため、flavorなしの flutter run は起動時に落ちます。VS Codeのlaunch.jsonや mise run タスクで各flavorを必ず渡す構成にしておくと安全です。 main.dartでduplicate-appを処理する Firebaseには2つの初期化経路があります。ネイティブの自動初期化と、Dart側の Firebase.initializeApp() です。両方が呼ばれると duplicate-app の FirebaseException が発生します。ただし duplicate-app はflavor不整合(ネイティブのplist/jsonが別のFirebaseプロジェクトを指している等)でも発生しうるため、無条件で成功扱いにはできません。ネイティブ初期化済みの options と比較し、一致した場合のみ成功扱いにします。不一致の場合は例外を再throwし、アプリを起動時にクラッシュさせます。例外を握りつぶしてしまうと、不正なAPIキーや設定不整合といった検知したいバグを見逃してしまう可能性があるためです。 // app/lib/main.dart(抜粋) Future< bool > _initializeFirebase() async { try { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); return true ; } on UnimplementedError { // placeholder(setup:firebase 未実行)の場合は Firebase を無効化して続行 return false ; } on FirebaseException catch (e) { if (e.code == 'duplicate-app' ) { final existing = Firebase.app().options; final expected = DefaultFirebaseOptions.currentPlatform; final matchesNativeConfig = existing.appId == expected.appId && existing.projectId == expected.projectId && existing.apiKey == expected.apiKey; if (matchesNativeConfig) return true ; } rethrow ; } } 5. mise run setupで全flavorを一括生成する mise は、タスク実行とツールのバージョン管理を兼ねるCLIツールです。本テンプレートではセットアップスクリプトの実行に利用しています。手作業の設定漏れを防ぐため、セットアップの入力ファイル app_config.yaml にFirebaseのproject_idをflavorごとに設定できる構造を追加しました。これにより、 mise run setup:firebase で全flavorの設定ファイルを一括生成できます。 flavor : dev : app_name_suffix : " Dev" bundle_id_suffix : ".dev" firebase_project_id : "my-project-dev" # 空文字なら Firebase 設定をスキップ stg : app_name_suffix : " Stg" bundle_id_suffix : ".stg" firebase_project_id : "my-project-stg" prd : app_name_suffix : "" bundle_id_suffix : "" firebase_project_id : "my-project-prd" mise run setup:firebase は app_config.yaml の各flavorの firebase_project_id を読み取ります。読み取った値で flutterfire configure をflavorごとに実行します。 iOSは --ios-out でパスを直接指定すると、plistは出力されるもののproject.pbxprojへの参照は Runner/ 配下を前提に追加されます。配置と参照のズレでビルドが落ちる事例もありました。そこで setup:firebase は flutterfire configure のデフォルト出力( Runner/ )を利用し、後処理で配置を整えます。 Build PhaseのRun Scriptがflavorに応じてplistを動的にコピーするので、 project.pbxproj の静的参照は不要です。 setup:firebase は実行のたびに追記される参照を毎回除去し、pbxprojが肥大化しないようにしています。 # クリーンアップ確認(0 であれば OK) grep -c " GoogleService-Info.plist .*PBXBuildFile " \ app/ios/Runner.xcodeproj/project.pbxproj mise run setup:flavor はxcconfigやflavor JSONを app_config.yaml から再生成します。Firebaseの設定ファイルは別途 setup:firebase で更新します。何度実行しても同じ出力になるため、設定変更後に再実行すればxcconfigとflavor JSONが同期されます。 この仕組みにより、新規プロジェクトでは app_config.yaml を編集して mise run setup を実行するだけで済みます。iOS xcconfig・Android productFlavors・Dartセレクタshim・Firebase設定ファイルのすべてが一括でできあがります。 まとめ 本記事では、FlutterプロジェクトにおけるCocoaPodsからSPMへの移行を紹介しました。あわせてdev / stg / prdの3 flavorをiOS・Android・Dartの3層で整合させる仕組みの構築も紹介しました。 CocoaPodsからSPMへの移行自体は pod deintegrate → Podfile削除で思ったよりシンプルです。手間がかかるのはflavorの3層整合で、それぞれが独立した設定体系を持つため一つひとつ繋ぎ込む必要があります。 app_config.yaml → mise run setup という自動化の仕組みを整えました。これにより手動作業による設定漏れのリスクを排除し、チームが本来の開発に集中できる環境を作れました。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
チラシやポスター、商品パッケージ、店頭POPなど、印刷物からWebサイトへ誘導する手段として、QRコードは広く利用されています。 手軽に作成でき、スマートフォンで簡単に読み取れる。その便利さから、 QRコードそのものの品質や確認工程 については、十分に意識されないまま制作が進んでしまうことがあります。 しかし、QRコードは印刷後に不具合が見つかっても、 Webページのようにその場で修正することはできません 。 読み取れないQRコードが印刷されてしまえば、 チラシやパッケージ、販促物の刷り直し が必要になり、印刷費だけでなく、 納期の遅延、廃棄コスト、キャンペーン機会の損失、取引先からの信用低下 につながる可能性があります。 株式会社モンテカンポ では、こうした印刷後のトラブルを防ぐために、 QRコードの作成と実機検証を行うサービス を提供しています。 QRコードのトラブルは、なぜ印刷後に発覚するのか 印刷物の制作現場では、 複数の会社や担当者が関わることが一般的 です。 発注元は「制作会社が確認しているはず」と考え、制作担当者は「自分のスマートフォンで読めたから問題ない」と判断し、印刷会社は「支給されたデータをそのまま印刷する」という立場を取ることがあります。 その結果、QRコードの品質を 誰が最終的に保証するのかが曖昧なまま 、入稿まで進んでしまいます。 パソコンの画面上で問題なく見えていても、実際に印刷すると、サイズや解像度、印刷状態、掲載する素材などの影響によって、読み取りにくくなる場合があります。 一台のスマートフォンで読み取れたとしても、すべての利用者が同じ機種、同じOS、同じカメラを使用するわけではありません。 QRコードは、作成するだけではなく、 実際の利用環境を想定して確認することが重要 です。 無料のQRコード作成ツールに潜むリスク インターネット上には、URLを入力するだけでQRコードを作成できる無料ツールが数多くあります。 一時的な案内や、すぐに差し替えられるWeb上の画像であれば、こうしたツールが便利な場面もあります。 一方、数千部、数万部を印刷するチラシやパッケージに掲載するQRコードでは、「 画面上では読み取れた 」という確認だけで入稿するのは危険です。 QRコードには、コードの位置や向きを認識するための「位置検出パターン」があります。生成した画像を不適切な方法で拡大・縮小したり、デザインデータ上でサイズを変更したりすると、 読み取りに必要な比率が崩れ 、認識に時間がかかったり、読み取れなくなったりする可能性があります。 また、URLが長い場合は QRコードの情報密度が高くなり 、印刷サイズや印刷品質によっては読み取りにくくなることがあります。 問題は、無料ツールそのものだけではありません。 「 誰が、どのツールで、どのような条件で作成し、どの環境で確認したのか 」が管理されていないことが、大きなリスクになります。 一度の確認不足が、大きな損失につながる QRコードの不具合は、一見すると小さな制作ミスに思えるかもしれません。 しかし、キャンペーン用の印刷物がすべて使用できなくなれば、損害は印刷費だけにとどまりません。 たとえば、次のような影響が考えられます。 印刷物の全数廃棄 再印刷費用の発生 納品・配布スケジュールの遅延 キャンペーン開始日の変更 店舗や関係各社への再手配 クライアントからの信用低下 問い合わせやクレーム対応の増加 添付資料では、無料ツールで作成したQRコードを十分に確認せず入稿した結果、印刷後にアクセスできないことが判明し、 1,000万円を超える損害 につながった事例も紹介されています。 大規模な案件ほど、QRコード一つの不具合が、 事業上の大きな事故に発展する可能性 があります。 入稿前に確認したい5つのポイント QRコードを印刷物に掲載する際は、少なくとも次の項目を確認する必要があります。 1.十分な掲載サイズが確保されているか QRコードが小さすぎると、スマートフォンのカメラが認識しにくくなります。 資料では、コード領域として20mm以上を確保することが推奨されており、10mm以下は特に注意が必要とされています。 掲載スペースだけで判断せず、実際に読み取れるサイズであるかを確認することが重要です。 2.埋め込むURLが長すぎないか QRコードは、埋め込む情報量が多くなるほど模様が細かくなります。 長いURLをそのまま使用するとコードの密度が高くなり、小さな印刷物や印刷条件によっては読み取り性能に影響する可能性があります。 3.実際の印刷物で確認したか 制作画面やPDF上で読み取れることと、印刷後に読み取れることは同じではありません。 可能であれば、校正刷りや実際に近い印刷条件で出力し、読み取り確認を行う必要があります。 4.複数の端末で確認したか 一台のスマートフォンだけで確認を終えるのではなく、iPhoneとAndroid、異なる機種やOS、標準カメラなど、複数の環境で検証することが望まれます。 5.作成元と確認責任者が明確か 担当者が個人的に見つけた無料ツールで作成し、誰も生成条件を把握していない状態は避けるべきです。 どのツールを使い、誰が作成し、誰が検証したのかを記録できるフローを整えることで、確認漏れを防ぎやすくなります。 QRコードの事故を防ぐには、個人の注意力に頼らない 繁忙期や納期直前には、どれほど経験のある担当者でも確認漏れを起こす可能性があります。 そのため、組織として次のようなルールを設けることが有効です。 印刷物に使用するQRコードは、担当者個人が無料ツールで作成しない 指定された方法で生成し、実機検証を終えてから入稿する QRコードの作成者と確認者を分ける 校正時のチェック項目にQRコードの読み取り確認を入れる 確認工程をルール化すれば、担当者の経験や忙しさに左右されにくくなり、トラブルを構造的に防ぐことができます。 株式会社モンテカンポの「QRコード作成&検証サービス」 株式会社モンテカンポは、 ソフトウェアテストと第三者検証を専門とする会社 です。 QRコード作成&検証サービスでは、正規ツールを使用してQRコードを生成し、実際のスマートフォン端末で読み取り確認を行います。 社内で専門的な確認環境を用意することが難しい場合 や、 大量印刷を伴う重要な案件 、 失敗できないキャンペーン などで、外部の検証サービスを活用できます。 サービス内容 QRコードの作成 正規ツールを使用した生成 スマートフォン実機での読み取り確認 案件規模や予算に応じた検証範囲の調整 キャンペーンサイトを含む外部検証の相談 料金は、 QRコード1個につき20,000円(税別) からです。 大がかりな検証だけでなく、範囲を絞った簡易的な対応についても相談できます。 QRコードを印刷する前に、専門家による確認を チラシ、ポスター、パッケージ、POPなどに掲載するQRコードについて、次のような不安はありませんか。 無料ツールで作成したQRコードを使用している 誰が作成したデータなのか分からない 一台のスマートフォンでしか確認していない 校正刷りで読み取り確認をしていない 大量印刷を予定しており、失敗が許されない クライアントに提出できる確認体制を整えたい 一つでも当てはまる場合は、入稿前に作成方法と検証工程を確認することをおすすめします。 株式会社モンテカンポでは、QRコードの作成・検証に加え、キャンペーンサイトの外部検証についても相談を受け付けています 。 印刷後に後悔する前に、まずは案件の内容や印刷条件、必要な確認範囲をご相談ください。 QRコード作成&検証サービス 料金:1個20,000円(税別)から 提供:株式会社モンテカンポ 所在地:東京都港区西新橋2-13-6 ミタニビル3階 電話:03-5510-8991 担当:中村 無料相談予約: https://calendar.app.google/S7vdSYCYgvPPh8vL8

動画

書籍