ข้ามไปยังเนื้อหา

Production Concerns

ใน production คุณอยากรู้เรื่อง crash ก่อน user จะบ่น error ของ Flutter มาถึงผ่านสอง channel และคุณต้องมีทั้งคู่:

void main() {
// 1. Errors thrown inside the Flutter framework (build, layout, paint).
FlutterError.onError = (details) {
FlutterError.presentError(details);
reportToCrashService(details.exception, details.stack);
};
// 2. Errors outside the framework (async gaps, callbacks) — a guarded zone.
runZonedGuarded(() {
runApp(const MyApp());
}, (error, stack) {
reportToCrashService(error, stack);
});
}

FlutterError.onError จับ error ที่ framework raise ส่วน runZonedGuarded จับที่เหลือทั้งหมด (uncaught async error, isolate error ผ่าน PlatformDispatcher.instance.onError) คุณยังแทน ErrorWidget สีแดงด้วย fallback ที่เป็นมิตรได้ เพื่อไม่ให้ widget เดียวที่พังโชว์หน้า crash ให้ user เห็น

ต่อ handler พวกนั้นเข้ากับ service — Sentry, Firebase Crashlytics — เพื่อให้ crash จาก device จริงกลับมาพร้อม stack trace, ข้อมูล device และ breadcrumb กฎสองข้อที่สำคัญ:

  • Upload symbol/debug info สำหรับ release build (AOT ตัด symbol ออก; ถ้าไม่มี mapping stack trace จะเป็น address ที่ถูก obfuscate อ่านไม่ออก)
  • Log อย่างตั้งใจ print ถูกตัด/เพิกเฉยใน release; ใช้ logger จริงและ scrub ข้อมูล sensitive ก่อนออกจาก device

ความลื่นคือ budget: ~16ms ต่อ frame สำหรับ 60fps, ~8ms สำหรับ 120fps พลาดแล้ว frame จะ “jank” วินิจฉัยใน profile mode (อย่าใช้ debug):

flowchart LR
  build["build
(your widgets)"] --> layout["layout"] --> paint["paint"] --> raster["raster
(GPU)"]
  raster --> budget["must fit ~16ms
or the frame janks"]
เวลาของ frame ไปไหนบ้าง

timeline ของ DevTools แสดง cost ต่อ frame แยกเป็น UI (Dart: build/layout/paint) และ raster (GPU) ตัวการยอดฮิต: rebuild เยอะเกิน (ลืม const, state อยู่สูงเกินใน tree), งานแพงใน build, รูปใหญ่ที่ไม่ cache และการลืม RepaintBoundary รอบ subtree ที่ animate ตลอด

automate pipeline เพื่อให้คุณภาพไม่ขึ้นกับความจำ: ทุก push ให้รัน flutter analyze, flutter test แล้ว flutter build แต่ละ platform และ ship ไป TestFlight / Play internal track เครื่องมืออย่าง Codemagic, Fastlane และ GitHub Actions จัดการเรื่อง signing และ store upload เฉพาะของ Flutter ให้

production checklist:

  • ต่อ global error handling (FlutterError.onError + runZonedGuarded) เข้ากับ crash service พร้อม upload symbol
  • มี ErrorWidget fallback ที่เป็นมิตรใน release
  • profile ใน profile mode; ไม่มี jank ชัด ๆ บน device เป้าหมาย
  • มี flavor สำหรับ dev/staging/prod; ไม่มี secret ใน source (--dart-define / secure storage)
  • CI รัน analyze + test + build ทุกการเปลี่ยนแปลง
ทำไมต้องมีทั้ง FlutterError.onError และ runZonedGuarded?
ทำไมต้อง upload symbol/debug info สำหรับ release crash report?
ควร profile performance ใน mode ไหน?
อะไรคือสาเหตุ jank ที่พบบ่อย?