flutter-startup-gate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter startup gate

Flutter 启动闸门

Stop startup crashes where the UI reads a
late
field before an async
init()
has assigned it. The fix is a gate in the root widget: render a splash until
initialized
, an error screen with retry when init fails, and the app shell only after.
解决UI在异步
init()
完成赋值前读取
late
字段导致的启动崩溃问题。解决方案是在根组件中添加一个闸门:完成初始化前显示启动页,初始化失败时显示带重试按钮的错误页面,仅在初始化完成后显示应用外壳。

Steps

步骤

  1. Confirm the race. The crash
    LateInitializationError: Field 'repo' has not been initialized
    (or similar) means a widget read a
    late
    field in the first frame, before
    init()
    's awaits finished. It may pass on a fast host and only crash on slow real devices — check
    logcat
    for
    E/flutter
    on-device.
  2. Add an init gate to the root widget (
    app.dart
    ):
    • While
      initializing
      → show a splash (logo + spinner + "Opening…").
    • If
      error != null
      → show an error screen with a Retry button (do not crash).
    • Only when
      initialized
      → show the app shell. Completion criterion: every first-frame widget sits behind the gate; none touch
      repo
      /services before the gate opens.
  3. Guard against double-init. Add a
    _initStarted
    flag so
    init()
    can't run twice (hot reload / retry). Expose
    retryInit()
    that resets state and re-runs
    init()
    . Completion criterion: tapping Retry re-runs init and reaches the shell without a second manual launch.
  4. Verify.
    flutter analyze
    clean;
    flutter test
    passes; reinstall on the real device and confirm logcat shows no
    LateInitializationError
    on cold start (see
    flutter-device-smoke-test
    ).
  1. 确认竞争条件:崩溃信息
    LateInitializationError: Field 'repo' has not been initialized
    (或类似信息)表示组件在第一帧就读取了
    late
    字段,而此时
    init()
    中的await操作尚未完成。该问题在性能较好的主机上可能不会出现,仅在性能较慢的真实设备上崩溃——请查看设备上的
    logcat
    中的
    E/flutter
    日志。
  2. 给根组件(
    app.dart
    )添加初始化闸门
    • initializing
      状态时 → 显示启动页(Logo + 加载动画 + “正在启动…”)。
    • 如果
      error != null
      → 显示带重试按钮的错误页面(不要崩溃)。
    • 仅当
      initialized
      状态时 → 显示应用外壳。 完成标准:所有首帧组件都处于闸门之后;在闸门打开前,没有组件会访问
      repo
      /服务。
  3. 防止重复初始化:添加
    _initStarted
    标志,确保
    init()
    不会运行两次(热重载/重试场景)。暴露
    retryInit()
    方法,用于重置状态并重新运行
    init()
    。 完成标准:点击重试按钮会重新执行初始化流程,并无需手动重启应用即可进入应用外壳。
  4. 验证
    flutter analyze
    无问题;
    flutter test
    测试通过;在真实设备上重新安装应用,确认冷启动时
    logcat
    中没有
    LateInitializationError
    信息(可参考
    flutter-device-smoke-test
    )。

Reference

参考信息

  • Root cause pattern:
    AppState.init()
    is async (opens SQLite, wires services, assigns
    late
    fields);
    MaterialApp
    builds and screens call
    app.repo...
    in
    build()
    before the first await resolves.
  • The gate doubles as a user-visible loading state and an error boundary — both needed on slow devices.
  • 根本原因模式:
    AppState.init()
    是异步方法(打开SQLite数据库、连接服务、为
    late
    字段赋值);
    MaterialApp
    进行构建,且页面在第一个await操作完成前就在
    build()
    方法中调用
    app.repo...
  • 该闸门同时兼具用户可见的加载状态和错误边界的功能——这两者在性能较慢的设备上都是必需的。