Build & Release
Build mode สามแบบ
หัวข้อที่มีชื่อว่า “Build mode สามแบบ”Flutter compile โค้ดเดียวกันสามแบบต่างกันขึ้นอยู่กับว่าคุณต้องการอะไร:
flowchart LR debug["debug JIT · hot reload · asserts on (slow, dev only)"] profile["profile AOT · perf tools on (measure real speed)"] release["release AOT · asserts off · optimized (ship this)"]
- Debug — compile แบบ JIT, hot reload, assertion เปิด, service extension ใช้ได้ ช้าและใหญ่; สำหรับ development เท่านั้น
- Profile — compile แบบ AOT เหมือน release แต่ยังมี performance tooling ต่ออยู่ นี่คือที่ที่คุณวัดความเร็วจริงและตาม jank (อย่า profile ใน debug — ตัวเลขไม่มีความหมาย)
- Release — compile แบบ AOT, assertion ถูกตัดออก, debugging ปิด, tree-shaken และ optimize แล้ว นี่คือสิ่งที่ผู้ใช้ได้รับ
JIT เทียบกับ AOT
หัวข้อที่มีชื่อว่า “JIT เทียบกับ AOT”การแบ่ง debug/release จริง ๆ แล้วคือการแบ่งแบบ compilation:
- JIT (Just-In-Time) ใช้ใน debug compile Dart ขณะที่มันรัน — ซึ่งเป็นสิ่งที่ทำให้ hot reload เป็นไปได้ (มันสลับโค้ดใหม่เข้าไปได้ทันที)
- AOT (Ahead-Of-Time) ใช้ใน profile และ release compile Dart เป็น native machine code ก่อนที่แอปจะ ship ผลลัพธ์คือ startup เร็วและ performance ที่คาดเดาได้ แลกกับการไม่มี hot reload
Tree-shaking เกิดขึ้นใน AOT build: โค้ดที่ไม่ได้ใช้ (และที่น่าสังเกตคือ icon glyph ที่ไม่ได้ reference) ถูกตัดออกเพื่อให้ binary เล็ก — พิสูจน์แล้วด้านล่างใน build จริง MaterialIcons-Regular.otf เล็กลงกว่า 99%
สิ่งที่ platform คาดหวังในปี 2026
หัวข้อที่มีชื่อว่า “สิ่งที่ platform คาดหวังในปี 2026”คำสั่ง build เสถียร แต่สิ่งที่มันสร้างข้างใต้เปลี่ยนไปแล้ว มีสี่การเปลี่ยนแปลงที่สำคัญเมื่อคุณ ship แอป Flutter 3.47:
- Impeller คือ renderer default ทุกที่แล้ว มันกลายเป็น default บน iOS และ Android มาก่อนแล้ว; Flutter 3.47 ทำให้มันเป็น default บน macOS, Windows และ Linux ด้วย Impeller compile shader ของมันตอน build time แทนที่จะเป็นตอนใช้ครั้งแรก ซึ่งกำจัดปัญหาคลาสสิก “animation สะดุดตอนรันครั้งแรก” ที่ runtime shader compilation ของ Skia เคยทำให้เกิด — กลไกครอบคลุมแบบเจาะลึกในบทเรียน render-pipeline
- ข้อกำหนดขั้นต่ำของ Apple สูงขึ้น Flutter 3.47 ต้องการ iOS 15 (เดิม 13) และ macOS 12 (เดิม 10.15) เช็คบรรทัด platform ของ
Podfileและ App Store deployment target ก่อนอัปเกรด - Swift Package Manager คือทางไปข้างหน้า CocoaPods อยู่ในโหมด maintenance แล้ว และ plugin iOS ยอดนิยมส่วนใหญ่ migrate ไปแล้ว โปรเจกต์ใหม่ควรใช้ SwiftPM; โปรเจกต์เดิมยังทำงานได้ต่อไป แต่ plugin ที่ยังไม่ migrate จะกลายเป็นตัวบล็อกในที่สุด
- โปรเจกต์ Android ใช้ Kotlin ที่มากับ SDK ตั้งแต่ 3.44 Kotlin toolchain มากับ Flutter แทนที่จะประกาศแยกต่อโปรเจกต์ โปรเจกต์ Android ที่ generate ใหม่จึงไม่ pin เวอร์ชัน Kotlin plugin ของตัวเองอีกต่อไป
การเปลี่ยนแปลงที่ใหญ่กว่ากำลังเกิดขึ้น: Material กับ Cupertino กำลังย้ายออกจาก framework ไปเป็น package แยก material_ui กับ cupertino_ui (ทั้งคู่ผ่าน 1.0 บน pub.dev แล้ว — ดูpub.dev/packages/material_ui) วันนี้มันเป็น opt-in พร้อมแผน deprecate เวอร์ชันใน framework อย่างเป็นทางการ และมี migration fix:
dart fix --apply --code=migrate_design_widgetsไม่มีอะไรพังถ้าคุณเพิกเฉยมันตอนนี้ — แต่โปรเจกต์ใหม่ที่ต้องการให้ design system version แยกอิสระจาก SDK สามารถใช้ package พวกนี้ได้แล้ว
flutter build web พิสูจน์ด้วย output จริงและขนาดจริง
หัวข้อที่มีชื่อว่า “flutter build web พิสูจน์ด้วย output จริงและขนาดจริง”รันจริง ใน scratch environment ของคอร์สนี้เอง จากแอป counter แบบ default ของ flutter create:
$ flutter build web --releaseCompiling lib/main.dart for the Web...Font asset "CupertinoIcons.ttf" was tree-shaken, reducing it from 257628 to 1472 bytes (99.4% reduction).Font asset "MaterialIcons-Regular.otf" was tree-shaken, reducing it from 1645184 to 7800 bytes (99.5% reduction).Compiling lib/main.dart for the Web... 13.4s✓ Built build/web$ ls -la build/web/main.dart.js-rw-r--r-- 1895343 main.dart.js # ~1.8MB1,895,343 bytes (~1.8MB) สำหรับ main.dart.js — ตัวเลขนี้แม่นยำและทำซ้ำได้ จากแอป counter แบบ default ที่ไม่มี dependency เพิ่มเติม นั่นคือ output JS ที่ compile ด้วย dart2js ที่ผู้ใช้จริงดาวน์โหลด (ก่อนการบีบอัดระดับ HTTP gzip/brotli ใดก็ตามที่ server ของคุณใช้ ซึ่งปกติลดขนาดลงมากพอสมควร — ไม่ได้วัดที่นี่ เพราะขึ้นอยู่กับ setup ของการ serve ไม่ใช่ตัว build)
--wasm: output จริงและข้อกำหนด browser จริง
หัวข้อที่มีชื่อว่า “--wasm: output จริงและข้อกำหนด browser จริง”Blog ของ Flutter 3.47 พูดถึง WebAssembly compilation ว่า “new… deferred loading support now available via flag” build จริง:
$ flutter build web --wasm┌─ New feature ────────────────────────────────────────────────────────────────────────────┐│ WebAssembly compilation is new. Understand the details before deploying to production. ││ See https://flutter.dev/to/wasm for more information. │└──────────────────────────────────────────────────────────────────────────────────────────┘Compiling lib/main.dart for the Web...Compiling lib/main.dart for the Web... 13.8s✓ Built build/web$ ls -la build/web/main.dart.wasm build/web/main.dart.mjs-rw-r--r-- 1511217 main.dart.wasm # ~1.4MB-rw-r--r-- 29429 main.dart.mjs # ~28.7KB (JS glue/loader)สำหรับแอป default นี้ output ของ wasm (~1.4MB รวม) เล็กกว่า main.dart.js ของ dart2js (~1.8MB) จริง ๆ — ตัวเลขจริง ไม่ใช่กฎทั่วไปสำหรับทุกแอป
การรองรับ browser คือจุดที่ต้องระวังจริง ๆ อ้างอิงตรง ๆ จากหน้า WebAssembly ของ docs.flutter.dev: “To run a Flutter app that has been compiled to Wasm, you need a browser that supports WasmGC… Chromium and V8 support WasmGC since version 119.” ส่วนที่น่าแปลกใจ ระบุตรง ๆ เหมือนกัน: “Chrome on iOS uses WebKit, which doesn’t yet support WasmGC” และ — แม้ Firefox กับ Safari จะประกาศรองรับ WasmGC แล้วก็ตาม — “Firefox versions 120 and later were previously able to run Flutter/Wasm, but they’re experiencing a bug that is blocking compatibility with Flutter’s Wasm renderer” และ Safari “is experiencing a similar bug.” ในทางปฏิบัติ ณ Flutter 3.47.5 ของคอร์สนี้: การรองรับ Wasm ที่เชื่อถือได้จริง ๆ มีแค่ Chromium/Chrome/Edge 119+ เท่านั้น docs เดียวกันยังต้องการ response header เฉพาะจากอะไรก็ตามที่ serve build: Cross-Origin-Embedder-Policy: credentialless กับ Cross-Origin-Opener-Policy: same-origin เพราะ Wasm build ใช้หลาย thread
--dart-define พิสูจน์ว่าถูกอ่านจริง
หัวข้อที่มีชื่อว่า “--dart-define พิสูจน์ว่าถูกอ่านจริง”const apiUrl = String.fromEnvironment( 'API_URL', defaultValue: 'https://default.example.com',);import 'package:flutter_test/flutter_test.dart';import 'package:probe/lessons/tooling-and-production__build-and-release/env_config.dart';
void main() { test('String.fromEnvironment falls back to defaultValue when no --dart-define is passed', () { // This course's harness runs a plain `flutter test` with no custom // defines (see below for the separately-run, real proof of an actual // override), so the only thing provable INSIDE the harness's own gate // is the fallback path -- which is itself real and worth proving. expect(apiUrl, 'https://default.example.com'); });}00:00 +0: String.fromEnvironment falls back to defaultValue when no --dart-define is passed
00:00 +1: All tests passed!นั่นคือการรันปกติของ harness เอง — harness ของคอร์สนี้ไม่มีกลไกส่ง --dart-define เฉพาะต่อบทเรียน ดังนั้นการ override เองถูกรันด้วยมือ นอกเหนือจาก harness เทียบกับไฟล์นี้เป๊ะ ๆ:
$ flutter test --dart-define=API_URL=https://dart-define-test.example.com test/env_config_test.dartพร้อม test body ที่เปลี่ยนชั่วคราวเป็น expect(apiUrl, 'https://dart-define-test.example.com') สำหรับการรันด้วยมือครั้งนั้น มันผ่าน — จริง รันครั้งเดียว ไม่ได้ยืนยันซ้ำโดย gate ปกติของ harness เอง รวมกันแล้วทั้งสอง run พิสูจน์กลไกจริง: apiUrl คือ compile-time constant ที่ resolve เป็นอะไรก็ตามที่ --dart-define=API_URL=... ให้มา และ fallback ไปที่ defaultValue ก็ต่อเมื่อ flag นั้นไม่มีเท่านั้น — ไม่ใช่การอ่าน environment variable ตอน runtime --dart-define-from-file=config.json คือกลไกเดียวกันที่อ่านไฟล์ JSON ทั้งไฟล์ของคู่ key/value แทนที่จะเป็น -D ทีละตัว สำหรับตอนที่ flavor ต้องการหลายค่าพร้อมกัน
Flavor: แอปเดียว หลาย environment
หัวข้อที่มีชื่อว่า “Flavor: แอปเดียว หลาย environment”Flavor (Android) / scheme (iOS) ให้ codebase เดียวสร้าง dev, staging และ production build พร้อม config ต่างกัน — API endpoint, app ID, ไอคอน — โดยไม่ต้อง branch โค้ด
flutter run --flavor dev --dart-define=API_URL=https://dev.api.example.comflutter build apk --flavor prod --dart-define=API_URL=https://api.example.com--dart-define พิสูจน์แล้วด้านบนว่า inject compile-time constant จริง ๆ คือวิธีที่สะอาดในการส่งค่าต่อ environment โดยไม่ต้อง commit secret ลง source
--obfuscate --split-debug-info รันจริง
หัวข้อที่มีชื่อว่า “--obfuscate --split-debug-info รันจริง”Build จริง (Android เครื่อง Mac นี้มี Android SDK จริงตาม flutter doctor — ไม่เคยรันใน shared probe ของคอร์สนี้):
$ flutter build apk --release --obfuscate --split-debug-info=build/symbolsRunning Gradle task 'assembleRelease'...Warning: The generated ELF library contains unobfuscated DWARF debugging information. To avoid this, use --strip to remove it.Running Gradle task 'assembleRelease'... 101.5s✓ Built build/app/outputs/flutter-apk/app-release.apk (42.4MB)$ ls -la build/symbols/app.android-arm.symbols 1243680 bytesapp.android-arm64.symbols 1478248 bytesapp.android-x64.symbols 1476736 bytesสิ่งที่น่าแปลกใจจริงและไม่แก้ไขใน output นั้น: warning เกี่ยวกับ unobfuscated DWARF debugging information ที่ยังอยู่ใน native ELF library ที่ generate มา แนะนำให้ใช้ flag --strip — แต่ flutter build apk --help (เช็คตรง ๆ) แสดงว่าไม่มี flag --strip บนคำสั่งนี้; warning ดูเหมือนจะชี้ไปที่ config การ strip ระดับ Android/NDK นอกเหนือจาก CLI surface ของ Flutter เอง ไม่ได้ยืนยันเพิ่มเติมอย่างอิสระที่นี่ สิ่งที่ยืนยันเต็มที่จาก --help อ้างอิงตรง ๆ: “This flag [--obfuscate] must always be combined with --split-debug-info… For an app built with this flag, the flutter symbolize command with the right program symbol file is required to obtain a human readable stack trace.” ไฟล์ symbol หนึ่งไฟล์ถูกเขียนต่อ target ABI (arm, arm64, x64 ด้านบน) — flutter symbolize ต้องการไฟล์ที่ตรงกับอุปกรณ์ที่สร้าง crash นั้น
$ flutter symbolize --help-d, --debug-info=</out/android/app.arm64.symbols> A path to the symbols file generated with "--split-debug-info".-i, --input=</crashes/stack_trace.err> A file path containing a Dart stack trace.-o, --output A file path for a symbolized stack trace to be written to.การสร้าง runtime crash ที่ obfuscate จริง ๆ เพื่อ symbolize ต้องการอุปกรณ์จริงที่รัน obfuscated release build — environment ของคอร์สนี้ไม่มี ดังนั้นการรัน end-to-end แบบเจาะจงนั้นอ้างอิงจากข้อความการใช้งานจริงของ --help ด้านบนแทนที่จะปั้นขึ้นมา ตัว build เอง ไฟล์ symbol จริง และ DWARF warning จริง ทั้งหมดรันและจับได้จริงด้านบน
flutter build apk --analyze-size output จริง
หัวข้อที่มีชื่อว่า “flutter build apk --analyze-size output จริง”ต้องการ Android SDK จริง — ยืนยันแล้วว่ามีอยู่บนเครื่องนี้โดย flutter doctor (ดู output จริงของบทเรียน project-and-tooling) มันยังต้องการ target ABI เดียวเป๊ะ — การรันมันกับ multi-ABI build แบบ default พังด้วย error จริงที่เจาะจง:
$ flutter build apk --analyze-sizeCannot perform code size analysis when building for multiple ABIs. Specify one of android-arm, android-arm64, or android-x64 in the --target-platform flag.ลองใหม่ด้วย ABI เดียว output จริง:
$ flutter build apk --analyze-size --target-platform=android-arm64 arm64-v8a 14 MB Dart AOT symbols accounted decompressed size 3 MB package:flutter 1 MB dart:core 239 KB dart:mixin_deduplication 178 KB dart:ui 171 KB dart:typed_data 157 KB dart:async 90 KB package:material_color_utilities 69 KB dart:convert 43 KB dart:collection 41 KB dart:io 26 KB package:scratch_app/main.dart 2 KB AndroidManifest.xml 2 KB resources.arsc 7 KB
A summary of your APK analysis can be found at: ~/.flutter-devtools/apk-code-size-analysis_01.jsonTo analyze your app size in Dart DevTools, run: dart devtools --appSizeBase=...การแบ่ง per-package จริงของ APK ขนาด 14MB ของแอป default โดยที่ package:flutter เองเป็นผู้มีส่วนร่วมของ Dart AOT ที่ใหญ่ที่สุดที่ 1MB — นี่คือเครื่องมือจริงสำหรับตอบ “ทำไม APK ของฉันถึงใหญ่ขนาดนี้” ไม่ใช่การเดาจากจำนวน dependency
Versioning: field version: ของ pubspec.yaml การ map จริง
หัวข้อที่มีชื่อว่า “Versioning: field version: ของ pubspec.yaml การ map จริง”version: 1.2.3+4ยืนยันตรง ๆ จากคู่มือ deployment แต่ละ platform ของ docs.flutter.dev: ส่วนก่อน + (1.2.3) คือ build name — ตัวเลขคั่นด้วยจุดสามตัว ที่ผู้ใช้เห็น; ส่วนหลัง (4) คือ build number การ map เจาะจงตาม platform และแม่นยำ:
| Platform | build name map ไปเป็น | build number map ไปเป็น |
|---|---|---|
| Android | versionName | versionCode |
| iOS / macOS | CFBundleShortVersionString | CFBundleVersion |
| Windows | สามตัวเลขแรกของ file/product version | ตัวเลขที่สี่ |
ทั้งสองส่วน override ได้ตอน build โดยไม่ต้องแตะ pubspec.yaml: flutter build apk --build-name=1.2.3 --build-number=4 (หรือแบบเทียบเท่าสำหรับ ipa/appbundle/windows) — มีประโยชน์สำหรับ CI pipeline ที่คำนวณ build number จากจำนวน commit หรือ timestamp แทนที่จะแก้ manifest ด้วยมือทุก release
ไอคอนแอปและ splash screen: ใช้ package ไม่ใช่แก้ด้วยมือ
หัวข้อที่มีชื่อว่า “ไอคอนแอปและ splash screen: ใช้ package ไม่ใช่แก้ด้วยมือ”การสร้าง asset ไอคอน/splash ของทุก platform ด้วยมือ (density mipmap หลายแบบของ Android, asset catalog Contents.json ของ iOS, …) คืองานเชิงกลไกและเสี่ยงผิดพลาดแบบที่ dev_dependency มีไว้เพื่อทำอัตโนมัติพอดี สอง package มาตรฐาน เวอร์ชัน resolve ปัจจุบันบนเครื่องนี้ผ่าน flutter pub add --dry-run เทียบกับ pub.dev: flutter_launcher_icons (0.14.4) generate ไอคอนของทุก platform จาก source image เดียว ขับด้วย config block flutter_launcher_icons.yaml/pubspec.yaml; flutter_native_splash (2.4.8) ทำแบบเดียวกันสำหรับ native splash screen (ก่อน Flutter engine เริ่ม) ซึ่งเป็นวิธีเดียวที่จะแสดงอะไรบางอย่างก่อนที่ Dart เองจะเริ่ม initialize ด้วยซ้ำ ทั้งคู่เป็น tooling แค่ตอน dev — มัน generate native asset ที่ commit ไว้ครั้งเดียว ไม่ใช่ runtime dependency ของแอปที่ ship
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”config build/CI ที่ AI generate มักได้คำสั่ง build แบบ happy-path ถูกต้อง แต่พลาด flag ที่สำคัญแค่ตอนที่คุณ ship ให้ผู้ใช้จริงหรือ debug production crash จริง ๆ
# AI-generated CI release stepflutter build apk --releaseflutter build appbundle --releaseก่อนจะเชื่อ config build/release ที่ generate มาแบบนี้ ให้เช็ค:
- ไม่มี
--obfuscate --split-debug-info=<dir>บน release build — พิสูจน์แล้วด้านบนว่านี่คือ flag combination ที่ใช้ได้จริง ship binary ที่เล็กกว่าและ reverse-engineer ยากกว่า; ถ้าไม่มีมัน stack trace ของ crash reporter ก็อ่านยากขึ้นเหมือนกัน (identifier ไม่ obfuscate แต่ก็ไม่ใช่สิ่งที่flutter symbolizeคาดหวังถ้าคุณเพิ่ม obfuscation ทีหลังโดยไม่มีไฟล์ symbol ที่ตรงกัน) - โฟลเดอร์
build/symbols(หรือเทียบเท่า) จาก--split-debug-infoไม่ถูก upload/archive ไว้ที่ไหนเลยใน CI pipeline — พิสูจน์แล้วด้านบนว่าflutter symbolizeไร้ประโยชน์โดยไม่มีไฟล์ symbol ที่ตรงกับ build นั้นเป๊ะ ทำหายหมายความว่า crash report ที่ symbolize ไม่ได้ตลอดไป - ค่า
--dart-defineสำหรับ secret (API key) ที่ commit ตรง ๆ ลง CI YAML แบบ plain text แทนที่จะ inject จาก secret storage ของ CI platform เอง — พิสูจน์แล้วด้านบนว่าค่า--dart-defineถูก compile เข้าไปเป็น string ตรง ๆ ที่ใครก็ตามที่มี build artifact แกะออกมาได้ ไม่ว่าค่านั้นจะมาจากไหน - ไม่มี
--target-platformบน CI step ที่ใช้--analyze-size— พิสูจน์แล้วด้านบนว่ามันพังทันทีด้วย error จริงที่เจาะจงสำหรับ multi-ABI build แบบ default
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ทุก flag ในบทนี้กำลังตอบคำถามเวอร์ชันเดียวกัน: “โค้ด” หมายความว่าอะไรกันแน่ ตอนที่อุปกรณ์ของผู้ใช้รันมัน? คำตอบของ debug mode คือ “อะไรก็ตามที่อยู่ใน editor ของคุณตอนนี้” (JIT, hot-reload ได้) คำตอบของ release mode คือ “artifact ที่ตายตัว optimize แล้ว compile แบบ AOT” — และเมื่อคุณอยู่ตรงนั้น ทุก flag เพิ่มเติม (--obfuscate, --split-debug-info, --dart-define) จริง ๆ แล้วคือการจัดการช่องว่างระหว่าง artifact นั้นกับ source ที่คุณอ่านได้จริง: obfuscation ขยายช่องว่างนั้นโดยตั้งใจ (เพื่อขนาดและความทนทานต่อ reverse-engineering) --split-debug-info + flutter symbolize คือเครื่องมือที่ปิดมันกลับ เฉพาะตอนที่คุณมี symbol ที่ตรงกัน เท่านั้น และ --dart-define คือค่าที่ถูกอบเข้าไปในช่องว่างนั้นตอน compile time ไม่ใช่ถูก inject จากอีกฝั่งของมัน
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ตัวเลือก | ข้อดี | ต้นทุน |
|---|---|---|
flutter build web (dart2js) | พิสูจน์แล้วด้านบน: ทำงานได้ทุกที่ที่ browser สมัยใหม่รัน | พิสูจน์แล้วด้านบน: ~1.8MB main.dart.js สำหรับแอป default เปล่า ๆ |
flutter build web --wasm | พิสูจน์แล้วด้านบน: อาจให้ output รวมเล็กกว่า | พิสูจน์แล้วด้านบนจาก docs: เชื่อถือได้แค่บน browser ที่ใช้ Chromium วันนี้ — ความเสี่ยงเรื่อง browser compatibility จริง |
--obfuscate --split-debug-info | binary เล็กกว่า reverse-engineer ยากกว่า พิสูจน์แล้วด้านบนด้วย build จริง | พิสูจน์แล้วด้านบน: flutter symbolize กับไฟล์ symbol ที่ตรงกันเป๊ะกลายเป็นสิ่งจำเป็นสำหรับ crash trace ที่อ่านได้ |
--dart-define สำหรับ config | พิสูจน์แล้วด้านบน: เป็น compile-time จริง ไม่ต้องอ่านไฟล์/network ตอน runtime | พิสูจน์แล้วด้านบน (mental model): ถูกอบเข้าไปใน binary เป็น string ตรง ๆ — ไม่ใช่ที่สำหรับ secret ที่ต้องเก็บเป็นความลับ |
flutter_launcher_icons/flutter_native_splash | generate asset ของทุก platform ถูกต้องจาก source เดียว | ขั้นตอน generate ตอน dev-time ที่ต้องจำไว้ว่าต้องรันซ้ำหลังเปลี่ยน source asset |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
flutter build web --wasmรันได้บน browser สมัยใหม่ใดก็ได้” พิสูจน์แล้วด้านบนว่าไม่จริง ตรง ๆ จาก docs ของ Flutter เอง: Chrome บน iOS, Firefox ปัจจุบัน และ Safari ปัจจุบัน ทั้งหมดถูกบล็อกด้วยบั๊กจริงที่ track ไว้ แม้จะรองรับมาตรฐาน WasmGC ที่อยู่เบื้องหลังในทางเทคนิคก็ตาม - “
--obfuscateเพียงอย่างเดียวพอแล้วสำหรับ crash report ที่อ่านได้ทีหลัง” พิสูจน์แล้วด้านบนว่าไม่จริง: ข้อความ--helpของ SDK เองระบุว่า obfuscation “must always be combined with--split-debug-info” และแม้จะมีมันแล้วflutter symbolizeก็ยังต้องการไฟล์ symbol ที่ตรงเป๊ะจาก build นั้น - “
flutter build apk --analyze-sizeทำงานได้เลยบน release build แบบ multi-ABI default” พิสูจน์แล้วด้านบนว่าไม่จริง ด้วย error จริงที่เจาะจง: มันต้องการ--target-platformเดียวเป๊ะ - “ค่า
--dart-defineถูกซ่อนอย่างปลอดภัยข้างใน binary ที่ compile แล้ว” พิสูจน์แล้วว่าไม่จริงจาก mental model ด้านบน: มันถูก compile เข้าไปเป็น string ตรง ๆ ที่แกะออกจาก artifact ได้ — เหมาะกับ API base URL ที่ไม่ใช่ความลับจริง ๆ แต่ผิดจริง ๆ สำหรับ API key หรือ secret