BTS・SEVENTEEN・BLACKPINK・Stray Kids の4グループぶん、同じ構成のファンアプリを作っている。1本目をきちんと作り、残りは雛形として複製してグループを差し替える、という進め方をした。複製は確かに速い。ただし、複製したからこそ起きる事故というのが確実にある。ここに書くのは、実際に手元で起きて原因まで特定できた10箇所だけだ。
| # | 症状 | 気づけた方法 |
|---|---|---|
| 1 | 20言語の翻訳に前のグループの経歴文が残る | .arb と生成済みdartの両方をgrep |
| 2 | 起動直後のスプラッシュに旧ファンダム名 | 実機で起動して目視 |
| 3 | ロゴ文字がハートからはみ出す | 実機で目視 |
| 4 | 韓国語学習とメンバー別チャットが全員ぶん無反応 | id とデータのキーを突き合わせ |
| 5 | ビルドは通るがインストール直後にクラッシュ | adb logcat -b crash |
| 6 | ストア名が30文字上限を超える | 掲載名を文字数で数える |
| 7 | 塗りつぶしのハートがPlay規約に触れる | 全文検索(コメント内も) |
| 8 | 審査中の名称変更で差し戻し | 審査状態を先に確認 |
| 9 | AABにアフィリエイトIDが入っていない/他社IDが混入 | 展開してバイナリ直grep |
| 10 | アイコンを直したのに旧アイコンのAABを上げていた | 新旧AABのmd5比較 |
1〜3:複製すると「前のグループ」が必ず残る
いちばん多いのがこれで、しかも一度では取り切れない。グループ名とファンダム名でgrepすれば済むと思いがちだが、実際にはそれで出てこない場所に残る。
1つめ。BLACKPINK版の全20言語の翻訳ファイルに、BTSメンバーの経歴文が280キーぶん残っていた。見つかったのは2026年8月17日で、リリース候補としてビルド済みのAABが2本(v4・v5)できたあとのことだった。グループ名でgrepしても出ない。経歴文の中身が「リーダー」「ラッパー」といった一般名詞で、メンバー名はキー名の側(bioRm のような形)にしか入っていなかったからだ。同型の事故で別のアプリは実際にストア掲載を取り下げている。教訓は単純で、翻訳リソースは .arb と、そこから生成された app_localizations_*.dart の両方を見る。片方だけ直しても、もう片方が生きている。
2つめ。起動直後のスプラッシュに旧ファンダム名が出ていた。ワードマークがウィジェットのソースにハードコードされていて、名称一括置換の対象から外れていたためだ。BLACKPINK版なのに起動して最初に見えるのが「ARMY」という状態で、ソースの他の場所はすべて直っていた。ここは「一括置換で直る場所」と「直らない場所」の境目で、複製のたびに真っ先に見る場所になった。
3つめは事故というより設計漏れ。ロゴ文字はグループごとに長さが違う。同じ枠に置くと長いものがハートからはみ出す。文字を縮小して自動でおさめるようにして解決したが、雛形を作る段階で「差し替わる文字列は長さが変わる」と想定しておけば済んだ話だった。
4:機能が全員ぶん無反応になる、いちばん静かな事故
10箇所のうち、見つけにくさで言えばこれが最悪だった。韓国語学習は「メンバーidを鍵にして、そのメンバーが担当するレベルのフレーズを引く」という作りになっている。BTSは7人で、鍵も7つある。BLACKPINKは4人だ。
複製時にメンバー一覧だけを差し替えると、レベル対応表の鍵はBTSの7人ぶんのまま残る。すると新しいメンバーidで引いた結果が空になる。ここで落ちてくれれば気づけるのだが、落ちない。エラーも出ない。ただ何も起きないだけだ。BLACKPINK版とStray Kids版の両方で、韓国語学習とメンバー別チャットが全員ぶん無反応になっていた。
対策として検収項目に入れたのは、突き合わせる場所を3つに固定することだった。メンバー定義側のid、レベル対応表の鍵、そしてチャット画面でidを直接比較している箇所。この3つがそろっていなければ機能は静かに死ぬ、という形で書き残してある。
5:ビルドが通ることと、起動することは別
今回いちばん重かった事故で、2026年8月17日に見つかった。Stray Kids版がインストール直後にクラッシュして、一度も起動しなかった。クラッシュログに出ていたのは『ClassNotFoundException: com.kwavehub.stayhub.MainActivity』の一行だけだ。原因は、Androidのnamespaceを com.example.stay_hub から com.kwavehub.stayhub に変更したときに、MainActivity.kt のパッケージ宣言とディレクトリを元のまま置いていたこと。マニフェストの android:name=".MainActivity" は先頭のドットが namespace 基準の相対指定なので、namespace だけを変えると参照先のクラスが存在しなくなる。
怖いのは、この状態でビルドが通ることだ。署名も通る。ファイルサイズも正常。この記事の9番で書くアフィリエイトIDの検査も全部通っていた。検査項目は全部緑で、ただ一つ「起動するか」を誰も見ていなかった。
以後のルールは2つ。namespace や applicationId を変えたら MainActivity のパッケージ宣言とディレクトリも必ず一緒に動かす。そして提出前に必ず実機にインストールして起動し、クラッシュログにFATALが0件であることを目で見る。
6〜8:名前まわりで止まる
6つめ。Playのアプリ名には30文字の上限がある。そしてストアの掲載名と、端末のホーム画面に出る名前は別物だ。前者は検索に効くので情報を入れたくなり、後者は長いと切れる。両方を同じ文字列にしようとすると必ずどちらかが壊れる。実際の掲載名は次のような長さになった。
| アプリ | 掲載名 | 字数 |
|---|---|---|
| ARMY | BTS♡ARMY HUB-BTS App | 20 |
| CARAT | 17♡CARAT HUB-SEVENTEEN App | 26 |
| STAY | S・Kids♡STAY HUB-Stray Kids | 26 |
| BLINK | B・P♡BLINK HUB-BLACKPINK App | 27 |
溢れたときに削るのは補助的な語のほうで、グループ名は残す。略称に置き換えて字数を稼ぐ手もあるが、検索で拾われなくなるので採らなかった。公式に通用している略称がある場合だけ例外にしている。
7つめ。ハートの記号は白抜き(♡)なら通るが、塗りつぶしのほうはPlayの規約に触れる。見た目がほとんど同じで、字面では区別がつきにくいのが厄介なところだ。しかもソースのコメント内に残っているだけでも危ない。コメントは後で誰かがコピーして名称に持ち込む種になるからだ。この記事でも塗りつぶしのほうは文字として書かないようにしている。名称に記号を使うと決めた時点で、どの記号なら通るかを先に確定させておくほうがいい。
8つめ。名称の変更を審査中にやらない。差し戻しと再審査のリスクを取ることになる。そして名称を変えると直す場所が5つある。ストアの掲載名、端末に出るラベル、アプリ定義ファイルの説明文、量産ツール側の表示名、そしてアプリ内のスプラッシュ。最後の1つがいちばん忘れやすく、残ると起動直後に旧名が出る。2つめの事故と同じ場所だ。
9〜10:「ソースを直した」と「上げるファイルに入っている」は別物
この2つが、いま検収でいちばん重視している項目だ。どちらもソースを見ている限り絶対に見つからない。
9つめ。アプリに埋め込まれたアフィリエイトIDは、成果物であるAABの中身で確認する。手順としては、AABを展開してネイティブライブラリを取り出し、バイナリを直接grepする。ここで2つ落とし穴があった。ひとつは strings コマンドを通してから grep すると取りこぼすこと。実際に、正しいIDが入っているのに0件と誤検出した。もうひとつは圧縮された .aab をそのまま grep しても正しく出ないこと。展開してから見る必要がある。
正常な状態は「正しいIDが各CPUアーキテクチャに1件ずつ」で、対応アーキテクチャが3つあれば合計3件出る。0件でも4件でもなく3件、という形で期待値を決めておくと判定が機械的になる。この検査で実際に見つけたのは、日本以外の言語のときに販売サイトのドメインだけ海外向けに切り替えて、アフィリエイトIDは日本用のままにしていた、というものだった。リンクは正常に開くので画面上は何も起きず、ただ報酬だけが発生しない。海外のファンが多いアプリほど損が大きく、実測して比較するまで誰も気づけなかった。
10つめ。アイコンを変更したのに、ストアに上がっていたのは変更の3時間39分前にビルドされたAABだった。ソースは正しい。手元のアイコン画像も正しい。ただアップロードされたファイルが古かった。ビルドの時刻を見るだけでは足りず、新旧のAABのmd5を比べてはじめて別物だと確定できる。「直した」と「上げた」の間に、ビルドという工程がまるごと挟まっていることを忘れると起きる。
この検査には注意点がひとつある。3文字以下の短い語をバイナリでgrepしてはいけない。部分文字列として無数に当たるからだ。メンバー名を検査しようとして「RM」を探すと format や confirm に当たり、「Jin」は join や engine に当たる。短い語はバイナリではなくソース側で、単語の境界をつけて調べる。そこを混同すると、残っていないものが残っているように見えて、判定が止まる。
同じ理由で、アプリ名も成果物側で確認する必要がある。コンパイル済みのマニフェストはバイナリ形式なので、テキスト抽出では正しく取れない。名前の前後を切り出して旧バージョンと比較する、という原始的なやり方でしか確かめられなかった。
10箇所に共通していたこと
並べてみると、10箇所のうち6箇所は「複製したから起きた」もので、4箇所は「直したつもりが成果物に入っていなかった」ものだった。前者は雛形を作る側の問題で、差し替わる部分を一箇所に集めておけば減らせる。後者は工程の問題で、作った本人の目では減らない。
同じように雛形からファンアプリを量産する人がいれば、実機での起動確認と、成果物のバイナリ検査の2つだけは工程に固定することをすすめる。この2つを飛ばしても、ビルドは最後まで緑のまま通る。
この記事に関係するページ
記事で触れた機能はこちらから試せます。

