Get started
App Launch Pipeline: Flutter Store Approval Gates

App Launch Pipeline: Flutter Store Approval Gates

Get a Flutter app approved on the App Store and Google Play on the first try. Scripted gates block the real rejection causes before upload: dormant ad SDKs and tracking remnants (Apple 5.6), placeholder purpose strings, R8 strips that crash on launch, 16 KB alignment, cold-start survival, keystore in the repo, and listing text over official limits or with price, other-platform or medical wording. Rejection playbook, purpose-string templates, launch report. 18 injected defects, 18 caught.
#Engenharia#Produtividade#Produto
Avaliação
São necessárias mais avaliações
Vendas
0
Como usar
Baixar

What this is

A release pipeline for Flutter apps that treats store review like CI: every known rejection cause is a scripted gate with an exit code, and nothing gets uploaded until the report says PASS. Built from real App Store and Google Play rejections, not from the guideline index.

Download mode: you get the skill folder, drop it into your Claude Code skills, and say "release this app". Nothing leaves your machine.

The gates (all run from one launch.yml)

Gate Catches Store rule
preflight_android.sh com.example id, release signed with the debug key, keystore inside the repo (symlinks resolved) or committed, missing keep-rules file, hardcoded versionName, cleartext traffic, dormant ad SDK, tracking/AD_ID SDK while tracking: false, remote feature switches outside your allowlist, Google test ad unit in release source Play policy, Apple 5.6
preflight_ios.sh deployment target (major.minor), placeholder purpose strings, export compliance flag, missing PrivacyInfo.xcprivacy (ITMS-91053), ad SDK remnants, ATT present exactly when tracking: true, test-harness FLUTTER_TARGET left by flutter drive, NSAllowsArbitraryLoads, UIRequiresFullScreen. Plist parsing in Python, so it runs on Linux CI too Apple 5.1.1, 5.1.2, 5.6
apk_16kb_check.py 64-bit .so (arm64-v8a, x86_64) with PT_LOAD alignment under 16 KB or unaligned zip offset Play 16 KB page size requirement
r8_rename_ratio.py whole-library keep rules that defeat R8 (ad apps exempt, on purpose; the 25% floor is this skill's rule of thumb)
coldstart_smoke.sh release APK dies in 12 s, crash-buffer entry or ClassNotFound for YOUR package only the thing reviewers see first
listing_lint.py every locale against the official limits (Apple keywords are 100 BYTES), price words, other-platform mentions, medical vocabulary, superlatives, test/beta in names Apple 2.3, Play metadata policy, Play Health declaration
launch_report.py runs all of the above, writes LAUNCH_REPORT.md with PASS / FAIL / SKIPPED(reason) and the manual checklist. Exit 0 only when everything passed; skipped gates exit 3; stale build artifacts are skipped, not trusted

A gate that cannot run on your machine is reported as SKIPPED with the exact command to run elsewhere. It is never reported as passed.

Sample report (fresh flutter create project, before fixing anything)

| gate              | status  |
|-------------------|---------|
| Android preflight | FAIL    |   release buildType is signed with the DEBUG key
|                   |         |   key.properties missing: no upload keystore configured
| iOS preflight     | FAIL    |   IPHONEOS_DEPLOYMENT_TARGET below 15.0
|                   |         |   ITSAppUsesNonExemptEncryption not declared false
| Listing lint      | SKIPPED |   no store/ dir
| 16 KB alignment   | PASS    |   libapp.so, libflutter.so min PT_LOAD align=0x10000
| R8 rename ratio   | PASS    |
| Cold-start smoke  | SKIPPED |   no device connected

Full file in examples/sample-launch-report.md.

Proof it catches things

tests/mutation_test.py takes a clean Flutter project, makes it release-ready (baseline PASS on all three gates), then injects defects one at a time and asserts the right gate fails (or warns) with the right message, plus harness cases (relative-path call, missing adb, missing config):

[ok] A1 proguard file referenced but missing     [ok] I1 deployment target 12
[ok] A2 keystore inside repo                     [ok] I2 placeholder purpose string
[ok] A2b keystore inside repo via symlink        [ok] I3 arbitrary loads
[ok] A3 cleartext true                           [ok] I4 ATT while tracking:false
[ok] A4 dormant ad SDK (android)                 [ok] I5 dormant ad SDK (ios)
[ok] A5 remote switch is WARN with allowlist     [ok] I6 harness FLUTTER_TARGET
[ok] A6 hardcoded versionName                    [ok] I7 missing privacy manifest is WARN
[ok] A7 release signed with debug key            [ok] I8 flavor target is WARN
[ok] A8 com.example applicationId                [ok] I9 15.6 >= 15.5 passes
[ok] A9 commented minify line ignored            [ok] L1 play_full 4001 chars
[ok] A10 tracking SDK while tracking:false       [ok] L2 Android mention in Apple text + medical
[ok] ads:true with SDK passes ad check           [ok] L3 subtitle 31 chars
[ok] ads:true test unit id FAILS                 [ok] L4 keywords 102 bytes
[ok] allowlisted remote config key passes        [ok] L5 "the #1 free app" in Play title
[ok] relative-path invocation still works        [ok] L6 iPhone mention in Play text
[ok] launch_report without adb writes report     [ok] launch_report without launch.yml exits 2
35/35 checks ok

You can rerun it yourself on any flutter create output.

Also inside

  • references/rejection-playbook.md: 20 rows of "rejection text you get -> real cause -> gate -> fix", including the first-subscription submission block on Apple, ITMS-91053 missing privacy manifest, and the Play "full description is too long" release failure.
  • references/listing-limits.md: every limit with its official Apple / Google URL.
  • references/purpose-strings.md: purpose strings that pass, in the four-part form reviewers accept.
  • launch.yml: four honest answers (ads, iap, health, package) drive every gate. Ad apps and ad-free apps are checked differently on purpose.

Requirements

  • Flutter project (Android + iOS). Scripts are bash + Python 3 standard library, no pip installs.
  • All gates run on macOS and Linux (plists are parsed with Python). Archiving still needs Xcode.
  • Cold-start smoke needs adb and a connected device or emulator; otherwise it is SKIPPED, not faked.

What it does not do

  • It does not sign, upload or submit for you, and it never touches signing certificates.
  • It does not write your listing. It tells you where the text breaks the rules.
  • It does not promise approval. It removes the known reasons for rejection and shows you the ones it could not check.