flutter-dart-code-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter/Dart Code Review Best Practices

Flutter/Dart代码审查最佳实践

Comprehensive, library-agnostic checklist for reviewing Flutter/Dart applications. These principles apply regardless of which state management solution, routing library, or DI framework is used.

这份全面、与库无关的清单用于审查Flutter/Dart应用。无论使用哪种状态管理方案、路由库或依赖注入框架,这些原则都适用。

1. General Project Health

1. 项目整体健康度

  • Project follows consistent folder structure (feature-first or layer-first)
  • Proper separation of concerns: UI, business logic, data layers
  • No business logic in widgets; widgets are purely presentational
  • pubspec.yaml
    is clean — no unused dependencies, versions pinned appropriately
  • analysis_options.yaml
    includes a strict lint set with strict analyzer settings enabled
  • No
    print()
    statements in production code — use
    dart:developer
    log()
    or a logging package
  • Generated files (
    .g.dart
    ,
    .freezed.dart
    ,
    .gr.dart
    ) are up-to-date or in
    .gitignore
  • Platform-specific code isolated behind abstractions

  • 项目遵循统一的文件夹结构(功能优先或分层优先)
  • 关注点正确分离:UI、业务逻辑、数据层相互独立
  • Widget中无业务逻辑;Widget仅负责展示
  • pubspec.yaml
    保持整洁——无未使用的依赖,版本已适当固定
  • analysis_options.yaml
    包含严格的lint规则集,并启用了严格的分析器设置
  • 生产代码中无
    print()
    语句——使用
    dart:developer
    log()
    或日志包
  • 生成文件(
    .g.dart
    .freezed.dart
    .gr.dart
    )已更新或已加入
    .gitignore
  • 平台特定代码通过抽象层隔离

2. Dart Language Pitfalls

2. Dart语言常见陷阱

  • Implicit dynamic: Missing type annotations leading to
    dynamic
    — enable
    strict-casts
    ,
    strict-inference
    ,
    strict-raw-types
  • Null safety misuse: Excessive
    !
    (bang operator) instead of proper null checks or Dart 3 pattern matching (
    if (value case var v?)
    )
  • Type promotion failures: Using
    this.field
    where local variable promotion would work
  • Catching too broadly:
    catch (e)
    without
    on
    clause; always specify exception types
  • Catching
    Error
    :
    Error
    subtypes indicate bugs and should not be caught
  • Unused
    async
    : Functions marked
    async
    that never
    await
    — unnecessary overhead
  • late
    overuse
    :
    late
    used where nullable or constructor initialization would be safer; defers errors to runtime
  • String concatenation in loops: Use
    StringBuffer
    instead of
    +
    for iterative string building
  • Mutable state in
    const
    contexts
    : Fields in
    const
    constructor classes should not be mutable
  • Ignoring
    Future
    return values
    : Use
    await
    or explicitly call
    unawaited()
    to signal intent
  • var
    where
    final
    works
    : Prefer
    final
    for locals and
    const
    for compile-time constants
  • Relative imports: Use
    package:
    imports for consistency
  • Mutable collections exposed: Public APIs should return unmodifiable views, not raw
    List
    /
    Map
  • Missing Dart 3 pattern matching: Prefer switch expressions and
    if-case
    over verbose
    is
    checks and manual casting
  • Throwaway classes for multiple returns: Use Dart 3 records
    (String, int)
    instead of single-use DTOs
  • print()
    in production code
    : Use
    dart:developer
    log()
    or the project's logging package;
    print()
    has no log levels and cannot be filtered

  • 隐式dynamic类型:缺少类型注解导致
    dynamic
    类型——启用
    strict-casts
    strict-inference
    strict-raw-types
  • 空安全误用:过度使用
    !
    (非空断言运算符),而非正确的空检查或Dart 3模式匹配(
    if (value case var v?)
  • 类型提升失败:在可以使用局部变量提升的地方使用
    this.field
  • 捕获范围过宽:使用
    catch (e)
    但无
    on
    子句;始终指定异常类型
  • 捕获
    Error
    Error
    子类表示程序错误,不应被捕获
  • 不必要的
    async
    :标记为
    async
    但从未使用
    await
    的函数——会产生不必要的开销
  • 过度使用
    late
    :在可以使用可空类型或构造函数初始化的场景使用
    late
    ;会将错误延迟到运行时
  • 循环中字符串拼接:使用
    StringBuffer
    替代
    +
    进行迭代式字符串构建
  • const
    上下文中的可变状态
    const
    构造函数类中的字段不可变
  • 忽略
    Future
    返回值
    :使用
    await
    或显式调用
    unawaited()
    以明确意图
  • 能用
    final
    却用
    var
    :局部变量优先使用
    final
    ,编译时常量优先使用
    const
  • 相对导入:使用
    package:
    导入以保持一致性
  • 暴露可变集合:公共API应返回不可修改的视图,而非原始的
    List
    /
    Map
  • 未使用Dart 3模式匹配:优先使用switch表达式和
    if-case
    ,而非冗长的
    is
    检查和手动类型转换
  • 为多返回值创建一次性类:使用Dart 3记录类型
    (String, int)
    替代一次性DTO
  • 生产代码中的
    print()
    :使用
    dart:developer
    log()
    或项目的日志包;
    print()
    无日志级别且无法过滤

3. Widget Best Practices

3. Widget最佳实践

Widget decomposition:

Widget拆分:

  • No single widget with a
    build()
    method exceeding ~80-100 lines
  • Widgets split by encapsulation AND by how they change (rebuild boundaries)
  • Private
    _build*()
    helper methods that return widgets are extracted to separate widget classes (enables element reuse, const propagation, and framework optimizations)
  • Stateless widgets preferred over Stateful where no mutable local state is needed
  • Extracted widgets are in separate files when reusable
  • 单个Widget的
    build()
    方法代码不超过80-100行
  • Widget按封装性和变化方式拆分(重建边界)
  • 将返回Widget的私有
    _build*()
    辅助方法提取为独立的Widget类(支持元素复用、const传播和框架优化)
  • 在无需可变局部状态的场景,优先使用Stateless Widget而非Stateful Widget
  • 可复用的提取Widget放在独立文件中

Const usage:

Const使用:

  • const
    constructors used wherever possible — prevents unnecessary rebuilds
  • const
    literals for collections that don't change (
    const []
    ,
    const {}
    )
  • Constructor is declared
    const
    when all fields are final
  • 尽可能使用
    const
    构造函数——避免不必要的重建
  • 对不变化的集合使用
    const
    字面量(
    const []
    const {}
  • 当所有字段均为final时,构造函数声明为
    const

Key usage:

Key使用:

  • ValueKey
    used in lists/grids to preserve state across reorders
  • GlobalKey
    used sparingly — only when accessing state across the tree is truly needed
  • UniqueKey
    avoided in
    build()
    — it forces rebuild every frame
  • ObjectKey
    used when identity is based on a data object rather than a single value
  • 在列表/网格中使用
    ValueKey
    以在重排序时保留状态
  • 谨慎使用
    GlobalKey
    ——仅在确实需要跨树访问状态时使用
  • 避免在
    build()
    中使用
    UniqueKey
    ——会强制每帧重建
  • 当身份基于数据对象而非单一值时,使用
    ObjectKey

Theming & design system:

主题与设计系统:

  • Colors come from
    Theme.of(context).colorScheme
    — no hardcoded
    Colors.red
    or hex values
  • Text styles come from
    Theme.of(context).textTheme
    — no inline
    TextStyle
    with raw font sizes
  • Dark mode compatibility verified — no assumptions about light background
  • Spacing and sizing use consistent design tokens or constants, not magic numbers
  • 颜色来自
    Theme.of(context).colorScheme
    ——不硬编码
    Colors.red
    或十六进制值
  • 文本样式来自
    Theme.of(context).textTheme
    ——不使用内联
    TextStyle
    和原始字体大小
  • 已验证深色模式兼容性——不假设浅色背景
  • 间距和尺寸使用统一的设计令牌或常量,而非魔法数字

Build method complexity:

Build方法复杂度:

  • No network calls, file I/O, or heavy computation in
    build()
  • No
    Future.then()
    or
    async
    work in
    build()
  • No subscription creation (
    .listen()
    ) in
    build()
  • setState()
    localized to smallest possible subtree

  • build()
    中无网络请求、文件I/O或繁重计算
  • build()
    中无
    Future.then()
    或异步操作
  • build()
    中无订阅创建(
    .listen()
  • setState()
    局限于最小的子树

4. State Management (Library-Agnostic)

4. 状态管理(与库无关)

These principles apply to all Flutter state management solutions (BLoC, Riverpod, Provider, GetX, MobX, Signals, ValueNotifier, etc.).
这些原则适用于所有Flutter状态管理方案(BLoC、Riverpod、Provider、GetX、MobX、Signals、ValueNotifier等)。

Architecture:

架构:

  • Business logic lives outside the widget layer — in a state management component (BLoC, Notifier, Controller, Store, ViewModel, etc.)
  • State managers receive dependencies via injection, not by constructing them internally
  • A service or repository layer abstracts data sources — widgets and state managers should not call APIs or databases directly
  • State managers have a single responsibility — no "god" managers handling unrelated concerns
  • Cross-component dependencies follow the solution's conventions:
    • In Riverpod: providers depending on providers via
      ref.watch
      is expected — flag only circular or overly tangled chains
    • In BLoC: blocs should not directly depend on other blocs — prefer shared repositories or presentation-layer coordination
    • In other solutions: follow the documented conventions for inter-component communication
  • 业务逻辑位于Widget层之外——在状态管理组件(BLoC、Notifier、Controller、Store、ViewModel等)中实现
  • 状态管理器通过注入接收依赖,而非内部构造
  • 服务或仓储层抽象数据源——Widget和状态管理器不应直接调用API或数据库
  • 状态管理器单一职责——无处理不相关事务的“上帝”管理器
  • 跨组件依赖遵循方案约定:
    • Riverpod:Provider通过
      ref.watch
      依赖其他Provider是预期行为——仅标记循环依赖或过度复杂的依赖链
    • BLoC:Bloc不应直接依赖其他Bloc——优先使用共享仓储或展示层协调
    • 其他方案:遵循文档中关于组件间通信的约定

Immutability & value equality (for immutable-state solutions: BLoC, Riverpod, Redux):

不可变性与值相等(适用于不可变状态方案:BLoC、Riverpod、Redux):

  • State objects are immutable — new instances created via
    copyWith()
    or constructors, never mutated in-place
  • State classes implement
    ==
    and
    hashCode
    properly (all fields included in comparison)
  • Mechanism is consistent across the project — manual override,
    Equatable
    ,
    freezed
    , Dart records, or other
  • Collections inside state objects are not exposed as raw mutable
    List
    /
    Map
  • 状态对象不可变——通过
    copyWith()
    或构造函数创建新实例,绝不原地修改
  • 状态类正确实现
    ==
    hashCode
    (比较时包含所有字段)
  • 项目中实现方式一致——手动重写、
    Equatable
    freezed
    、Dart记录或其他方式
  • 状态对象内的集合不暴露为原始可变的
    List
    /
    Map

Reactivity discipline (for reactive-mutation solutions: MobX, GetX, Signals):

响应式规范(适用于响应式可变方案:MobX、GetX、Signals):

  • State is only mutated through the solution's reactive API (
    @action
    in MobX,
    .value
    on signals,
    .obs
    in GetX) — direct field mutation bypasses change tracking
  • Derived values use the solution's computed mechanism rather than being stored redundantly
  • Reactions and disposers are properly cleaned up (
    ReactionDisposer
    in MobX, effect cleanup in Signals)
  • 仅通过方案的响应式API修改状态(MobX中的
    @action
    、Signals的
    .value
    、GetX的
    .obs
    )——直接字段修改会绕过变更追踪
  • 派生值使用方案的计算机制,而非冗余存储
  • 正确清理响应和销毁器(MobX中的
    ReactionDisposer
    、Signals中的effect清理)

State shape design:

状态结构设计:

  • Mutually exclusive states use sealed types, union variants, or the solution's built-in async state type (e.g. Riverpod's
    AsyncValue
    ) — not boolean flags (
    isLoading
    ,
    isError
    ,
    hasData
    )
  • Every async operation models loading, success, and error as distinct states
  • All state variants are handled exhaustively in UI — no silently ignored cases
  • Error states carry error information for display; loading states don't carry stale data
  • Nullable data is not used as a loading indicator — states are explicit
dart
// BAD — boolean flag soup allows impossible states
class UserState {
  bool isLoading = false;
  bool hasError = false; // isLoading && hasError is representable!
  User? user;
}

// GOOD (immutable approach) — sealed types make impossible states unrepresentable
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
  final User user;
  const UserLoaded(this.user);
}
class UserError extends UserState {
  final String message;
  const UserError(this.message);
}

// GOOD (reactive approach) — observable enum + data, mutations via reactivity API
// enum UserStatus { initial, loading, loaded, error }
// Use your solution's observable/signal to wrap status and data separately
  • 互斥状态使用密封类型、联合变体或方案内置的异步状态类型(如Riverpod的
    AsyncValue
    )——而非布尔标志(
    isLoading
    isError
    hasData
  • 每个异步操作将加载、成功、错误建模为不同状态
  • UI中穷举处理所有状态变体——无静默忽略的情况
  • 错误状态携带用于展示的错误信息;加载状态不携带过期数据
  • 不使用可空数据作为加载指示器——状态是显式的
dart
// 不良示例——布尔标志混乱会导致出现不可能的状态
class UserState {
  bool isLoading = false;
  bool hasError = false; // 会出现isLoading && hasError同时为true的情况!
  User? user;
}

// 良好示例(不可变方式)——密封类型避免出现不可能的状态
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
  final User user;
  const UserLoaded(this.user);
}
class UserError extends UserState {
  final String message;
  const UserError(this.message);
}

// 良好示例(响应式方式)——可观察枚举+数据,通过响应式API修改状态
// enum UserStatus { initial, loading, loaded, error }
// 使用方案的可观察对象/signal分别包装状态和数据

Rebuild optimization:

重建优化:

  • State consumer widgets (Builder, Consumer, Observer, Obx, Watch, etc.) scoped as narrow as possible
  • Selectors used to rebuild only when specific fields change — not on every state emission
  • const
    widgets used to stop rebuild propagation through the tree
  • Computed/derived state is calculated reactively, not stored redundantly
  • 状态消费Widget(Builder、Consumer、Observer、Obx、Watch等)的作用域尽可能小
  • 使用选择器仅在特定字段变更时重建——而非每次状态发射都重建
  • 使用
    const
    Widget阻止重建在树中传播
  • 计算/派生状态通过响应式计算,而非冗余存储

Subscriptions & disposal:

订阅与销毁:

  • All manual subscriptions (
    .listen()
    ) are cancelled in
    dispose()
    /
    close()
  • Stream controllers are closed when no longer needed
  • Timers are cancelled in disposal lifecycle
  • Framework-managed lifecycle is preferred over manual subscription (declarative builders over
    .listen()
    )
  • mounted
    check before
    setState
    in async callbacks
  • BuildContext
    not used after
    await
    without checking
    context.mounted
    (Flutter 3.7+) — stale context causes crashes
  • No navigation, dialogs, or scaffold messages after async gaps without verifying the widget is still mounted
  • BuildContext
    never stored in singletons, state managers, or static fields
  • 所有手动订阅(
    .listen()
    )在
    dispose()
    /
    close()
    中取消
  • 流控制器在不再需要时关闭
  • 计时器在销毁生命周期中取消
  • 优先使用框架管理的生命周期而非手动订阅(声明式构建器优于
    .listen()
  • 异步回调中调用
    setState
    前检查
    mounted
  • await
    后使用
    BuildContext
    前检查
    context.mounted
    (Flutter 3.7+)——过期上下文会导致崩溃
  • 在异步间隙后,导航、弹窗或脚手架消息前验证Widget仍处于挂载状态
  • 绝不将
    BuildContext
    存储在单例、状态管理器或静态字段中

Local vs global state:

局部状态 vs 全局状态:

  • Ephemeral UI state (checkbox, slider, animation) uses local state (
    setState
    ,
    ValueNotifier
    )
  • Shared state is lifted only as high as needed — not over-globalized
  • Feature-scoped state is properly disposed when the feature is no longer active

  • 临时UI状态(复选框、滑块、动画)使用局部状态(
    setState
    ValueNotifier
  • 共享状态仅提升到必要的层级——不过度全局化
  • 功能范围的状态在功能不再活跃时正确销毁

5. Performance

5. 性能

Unnecessary rebuilds:

不必要的重建:

  • setState()
    not called at root widget level — localize state changes
  • const
    widgets used to stop rebuild propagation
  • RepaintBoundary
    used around complex subtrees that repaint independently
  • AnimatedBuilder
    child parameter used for subtrees independent of animation
  • 不在根Widget层级调用
    setState()
    ——局部化状态变更
  • 使用
    const
    Widget阻止重建传播
  • 在独立重绘的复杂子树周围使用
    RepaintBoundary
  • AnimatedBuilder
    的child参数用于与动画无关的子树

Expensive operations in build():

Build()中的昂贵操作:

  • No sorting, filtering, or mapping large collections in
    build()
    — compute in state management layer
  • No regex compilation in
    build()
  • MediaQuery.of(context)
    usage is specific (e.g.,
    MediaQuery.sizeOf(context)
    )
  • build()
    中无排序、过滤或大型集合映射——在状态管理层计算
  • build()
    中无正则表达式编译
  • 精准使用
    MediaQuery.of(context)
    (如
    MediaQuery.sizeOf(context)

Image optimization:

图片优化:

  • Network images use caching (any caching solution appropriate for the project)
  • Appropriate image resolution for target device (no loading 4K images for thumbnails)
  • Image.asset
    with
    cacheWidth
    /
    cacheHeight
    to decode at display size
  • Placeholder and error widgets provided for network images
  • 网络图片使用缓存(使用适合项目的缓存方案)
  • 为目标设备使用合适的图片分辨率(不为缩略图加载4K图片)
  • Image.asset
    使用
    cacheWidth
    /
    cacheHeight
    以显示尺寸解码
  • 为网络图片提供占位符和错误Widget

Lazy loading:

懒加载:

  • ListView.builder
    /
    GridView.builder
    used instead of
    ListView(children: [...])
    for large or dynamic lists (concrete constructors are fine for small, static lists)
  • Pagination implemented for large data sets
  • Deferred loading (
    deferred as
    ) used for heavy libraries in web builds
  • 大型或动态列表使用
    ListView.builder
    /
    GridView.builder
    替代
    ListView(children: [...])
    (小型静态列表使用普通构造函数即可)
  • 大型数据集实现分页
  • Web构建中对重型库使用延迟加载(
    deferred as

Other:

其他:

  • Opacity
    widget avoided in animations — use
    AnimatedOpacity
    or
    FadeTransition
  • Clipping avoided in animations — pre-clip images
  • operator ==
    not overridden on widgets — use
    const
    constructors instead
  • Intrinsic dimension widgets (
    IntrinsicHeight
    ,
    IntrinsicWidth
    ) used sparingly (extra layout pass)

  • 动画中避免使用
    Opacity
    Widget——使用
    AnimatedOpacity
    FadeTransition
  • 动画中避免裁剪——提前裁剪图片
  • 不在Widget上重写
    operator ==
    ——使用
    const
    构造函数替代
  • 谨慎使用内在尺寸Widget(
    IntrinsicHeight
    IntrinsicWidth
    )——会增加额外布局步骤

6. Testing

6. 测试

Test types and expectations:

测试类型与预期:

  • Unit tests: Cover all business logic (state managers, repositories, utility functions)
  • Widget tests: Cover individual widget behavior, interactions, and visual output
  • Integration tests: Cover critical user flows end-to-end
  • Golden tests: Pixel-perfect comparisons for design-critical UI components
  • 单元测试:覆盖所有业务逻辑(状态管理器、仓储、工具函数)
  • Widget测试:覆盖单个Widget的行为、交互和视觉输出
  • 集成测试:覆盖关键用户端到端流程
  • Golden测试:对设计关键的UI组件进行像素级对比

Coverage targets:

覆盖率目标:

  • Aim for 80%+ line coverage on business logic
  • All state transitions have corresponding tests (loading → success, loading → error, retry, etc.)
  • Edge cases tested: empty states, error states, loading states, boundary values
  • 业务逻辑的行覆盖率目标80%+
  • 所有状态转换都有对应的测试(加载→成功、加载→错误、重试等)
  • 测试边缘情况:空状态、错误状态、加载状态、边界值

Test isolation:

测试隔离:

  • External dependencies (API clients, databases, services) are mocked or faked
  • Each test file tests exactly one class/unit
  • Tests verify behavior, not implementation details
  • Stubs define only the behavior needed for each test (minimal stubbing)
  • No shared mutable state between test cases
  • 外部依赖(API客户端、数据库、服务)已被模拟或伪造
  • 每个测试文件仅测试一个类/单元
  • 测试验证行为,而非实现细节
  • 存根仅定义每个测试所需的行为(最小化存根)
  • 测试用例间无共享可变状态

Widget test quality:

Widget测试质量:

  • pumpWidget
    and
    pump
    used correctly for async operations
  • find.byType
    ,
    find.text
    ,
    find.byKey
    used appropriately
  • No flaky tests depending on timing — use
    pumpAndSettle
    or explicit
    pump(Duration)
  • Tests run in CI and failures block merges

  • 正确使用
    pumpWidget
    pump
    处理异步操作
  • 适当使用
    find.byType
    find.text
    find.byKey
  • 无依赖时序的不稳定测试——使用
    pumpAndSettle
    或显式
    pump(Duration)
  • 测试在CI中运行,失败会阻止合并

7. Accessibility

7. 可访问性

Semantic widgets:

语义化Widget:

  • Semantics
    widget used to provide screen reader labels where automatic labels are insufficient
  • ExcludeSemantics
    used for purely decorative elements
  • MergeSemantics
    used to combine related widgets into a single accessible element
  • Images have
    semanticLabel
    property set
  • 在自动标签不足的场景,使用
    Semantics
    Widget为屏幕阅读器提供标签
  • 纯装饰元素使用
    ExcludeSemantics
  • 使用
    MergeSemantics
    将相关Widget合并为单个可访问元素
  • 图片设置
    semanticLabel
    属性

Screen reader support:

屏幕阅读器支持:

  • All interactive elements are focusable and have meaningful descriptions
  • Focus order is logical (follows visual reading order)
  • 所有交互元素可聚焦且有意义的描述
  • 聚焦顺序符合逻辑(遵循视觉阅读顺序)

Visual accessibility:

视觉可访问性:

  • Contrast ratio >= 4.5:1 for text against background
  • Tappable targets are at least 48x48 pixels
  • Color is not the sole indicator of state (use icons/text alongside)
  • Text scales with system font size settings
  • 文本与背景的对比度≥4.5:1
  • 可点击目标尺寸至少为48x48像素
  • 不单独使用颜色作为状态指示器(配合图标/文本)
  • 文本随系统字体大小设置缩放

Interaction accessibility:

交互可访问性:

  • No no-op
    onPressed
    callbacks — every button does something or is disabled
  • Error fields suggest corrections
  • Context does not change unexpectedly while user is inputting data

  • 无空实现的
    onPressed
    回调——每个按钮要么有功能,要么被禁用
  • 错误字段提供修正建议
  • 用户输入时,上下文不会意外变更

8. Platform-Specific Concerns

8. 平台特定注意事项

iOS/Android differences:

iOS/Android差异:

  • Platform-adaptive widgets used where appropriate
  • Back navigation handled correctly (Android back button, iOS swipe-to-go-back)
  • Status bar and safe area handled via
    SafeArea
    widget
  • Platform-specific permissions declared in
    AndroidManifest.xml
    and
    Info.plist
  • 适当使用平台自适应Widget
  • 正确处理返回导航(Android返回按钮、iOS滑动返回)
  • 通过
    SafeArea
    Widget处理状态栏和安全区域
  • AndroidManifest.xml
    Info.plist
    中声明平台特定权限

Responsive design:

响应式设计:

  • LayoutBuilder
    or
    MediaQuery
    used for responsive layouts
  • Breakpoints defined consistently (phone, tablet, desktop)
  • Text doesn't overflow on small screens — use
    Flexible
    ,
    Expanded
    ,
    FittedBox
  • Landscape orientation tested or explicitly locked
  • Web-specific: mouse/keyboard interactions supported, hover states present

  • 使用
    LayoutBuilder
    MediaQuery
    实现响应式布局
  • 统一定义断点(手机、平板、桌面)
  • 小屏幕上文本不溢出——使用
    Flexible
    Expanded
    FittedBox
  • 已测试横屏方向或已显式锁定
  • Web特定:支持鼠标/键盘交互,提供悬停状态

9. Security

9. 安全性

Secure storage:

安全存储:

  • Sensitive data (tokens, credentials) stored using platform-secure storage (Keychain on iOS, EncryptedSharedPreferences on Android)
  • Never store secrets in plaintext storage
  • Biometric authentication gating considered for sensitive operations
  • 敏感数据(令牌、凭证)使用平台安全存储(iOS的Keychain、Android的EncryptedSharedPreferences)
  • 绝不以明文存储机密信息
  • 敏感操作考虑生物识别认证 gate

API key handling:

API密钥处理:

  • API keys NOT hardcoded in Dart source — use
    --dart-define
    ,
    .env
    files excluded from VCS, or compile-time configuration
  • Secrets not committed to git — check
    .gitignore
  • Backend proxy used for truly secret keys (client should never hold server secrets)
  • API密钥不硬编码在Dart源码中——使用
    --dart-define
    、排除在VCS之外的
    .env
    文件或编译时配置
  • 机密信息不提交到git——检查
    .gitignore
  • 真正的机密密钥使用后端代理(客户端绝不持有服务器机密)

Input validation:

输入验证:

  • All user input validated before sending to API
  • Form validation uses proper validation patterns
  • No raw SQL or string interpolation of user input
  • Deep link URLs validated and sanitized before navigation
  • 所有用户输入在发送到API前验证
  • 表单验证使用正确的验证规则
  • 不使用原始SQL或用户输入的字符串插值
  • 深度链接URL在导航前验证和清理

Network security:

网络安全:

  • HTTPS enforced for all API calls
  • Certificate pinning considered for high-security apps
  • Authentication tokens refreshed and expired properly
  • No sensitive data logged or printed

  • 所有API调用强制使用HTTPS
  • 高安全性应用考虑证书固定
  • 正确刷新和过期认证令牌
  • 不记录或打印敏感数据

10. Package/Dependency Review

10. 包/依赖审查

Evaluating pub.dev packages:

评估pub.dev包:

  • Check pub points score (aim for 130+/160)
  • Check likes and popularity as community signals
  • Verify the publisher is verified on pub.dev
  • Check last publish date — stale packages (>1 year) are a risk
  • Review open issues and response time from maintainers
  • Check license compatibility with your project
  • Verify platform support covers your targets
  • 检查pub分数(目标130+/160)
  • 查看点赞数流行度作为社区信号
  • 验证发布者在pub.dev上已验证
  • 检查最后发布日期——过时包(>1年)存在风险
  • 查看开放问题和维护者的响应时间
  • 检查许可证与项目的兼容性
  • 验证平台支持覆盖目标平台

Version constraints:

版本约束:

  • Use caret syntax (
    ^1.2.3
    ) for dependencies — allows compatible updates
  • Pin exact versions only when absolutely necessary
  • Run
    flutter pub outdated
    regularly to track stale dependencies
  • No dependency overrides in production
    pubspec.yaml
    — only for temporary fixes with a comment/issue link
  • Minimize transitive dependency count — each dependency is an attack surface
  • 依赖使用脱字符语法(
    ^1.2.3
    )——允许兼容更新
  • 仅在绝对必要时固定确切版本
  • 定期运行
    flutter pub outdated
    跟踪过时依赖
  • 生产
    pubspec.yaml
    中无依赖覆盖——仅用于临时修复并附带注释/问题链接
  • 最小化传递依赖数量——每个依赖都是一个攻击面

Monorepo-specific (melos/workspace):

单仓库特定(melos/workspace):

  • Internal packages import only from public API — no
    package:other/src/internal.dart
    (breaks Dart package encapsulation)
  • Internal package dependencies use workspace resolution, not hardcoded
    path: ../../
    relative strings
  • All sub-packages share or inherit root
    analysis_options.yaml

  • 内部包仅从公共API导入——不使用
    package:other/src/internal.dart
    (违反Dart包封装)
  • 内部包依赖使用工作区解析,而非硬编码的
    path: ../../
    相对路径
  • 所有子包共享或继承根目录的
    analysis_options.yaml

11. Navigation and Routing

11. 导航与路由

General principles (apply to any routing solution):

通用原则(适用于任何路由方案):

  • One routing approach used consistently — no mixing imperative
    Navigator.push
    with a declarative router
  • Route arguments are typed — no
    Map<String, dynamic>
    or
    Object?
    casting
  • Route paths defined as constants, enums, or generated — no magic strings scattered in code
  • Auth guards/redirects centralized — not duplicated across individual screens
  • Deep links configured for both Android and iOS
  • Deep link URLs validated and sanitized before navigation
  • Navigation state is testable — route changes can be verified in tests
  • Back behavior is correct on all platforms

  • 统一使用一种路由方式——不混合命令式
    Navigator.push
    和声明式路由
  • 路由参数是类型化的——无
    Map<String, dynamic>
    Object?
    类型转换
  • 路由路径定义为常量、枚举或生成——无分散在代码中的魔法字符串
  • 认证守卫/重定向集中管理——不在各个屏幕重复实现
  • 为Android和iOS配置深度链接
  • 深度链接URL在导航前验证和清理
  • 导航状态可测试——测试中可验证路由变更
  • 所有平台上的返回行为正确

12. Error Handling

12. 错误处理

Framework error handling:

框架错误处理:

  • FlutterError.onError
    overridden to capture framework errors (build, layout, paint)
  • PlatformDispatcher.instance.onError
    set for async errors not caught by Flutter
  • ErrorWidget.builder
    customized for release mode (user-friendly instead of red screen)
  • Global error capture wrapper around
    runApp
    (e.g.,
    runZonedGuarded
    , Sentry/Crashlytics wrapper)
  • 重写
    FlutterError.onError
    以捕获框架错误(构建、布局、绘制)
  • 设置
    PlatformDispatcher.instance.onError
    以捕获Flutter未捕获的异步错误
  • 自定义
    ErrorWidget.builder
    用于发布模式(用户友好而非红屏)
  • runApp
    外包裹全局错误捕获(如
    runZonedGuarded
    、Sentry/Crashlytics包装器)

Error reporting:

错误上报:

  • Error reporting service integrated (Firebase Crashlytics, Sentry, or equivalent)
  • Non-fatal errors reported with stack traces
  • State management error observer wired to error reporting (e.g., BlocObserver, ProviderObserver, or equivalent for your solution)
  • User-identifiable info (user ID) attached to error reports for debugging
  • 集成错误上报服务(Firebase Crashlytics、Sentry或同类服务)
  • 上报非致命错误并附带堆栈跟踪
  • 状态管理器错误观察者连接到错误上报(如BlocObserver、ProviderObserver或对应方案的等价物)
  • 错误上报中附带用户标识信息(如用户ID)以辅助调试

Graceful degradation:

优雅降级:

  • API errors result in user-friendly error UI, not crashes
  • Retry mechanisms for transient network failures
  • Offline state handled gracefully
  • Error states in state management carry error info for display
  • Raw exceptions (network, parsing) are mapped to user-friendly, localized messages before reaching the UI — never show raw exception strings to users

  • API错误展示用户友好的错误UI,而非崩溃
  • 临时网络故障实现重试机制
  • 优雅处理离线状态
  • 状态管理中的错误状态携带用于展示的错误信息
  • 原始异常(网络、解析)在到达UI前映射为用户友好的本地化消息——绝不向用户展示原始异常字符串

13. Internationalization (l10n)

13. 国际化(l10n)

Setup:

配置:

  • Localization solution configured (Flutter's built-in ARB/l10n, easy_localization, or equivalent)
  • Supported locales declared in app configuration
  • 已配置本地化方案(Flutter内置的ARB/l10n、easy_localization或等价方案)
  • 应用配置中声明了支持的区域设置

Content:

内容:

  • All user-visible strings use the localization system — no hardcoded strings in widgets
  • Template file includes descriptions/context for translators
  • ICU message syntax used for plurals, genders, selects
  • Placeholders defined with types
  • No missing keys across locales
  • 所有用户可见字符串使用本地化系统——Widget中无硬编码字符串
  • 模板文件包含翻译人员所需的描述/上下文
  • ICU消息语法用于复数、性别、选择
  • 占位符已定义类型
  • 所有区域设置无缺失的键

Code review:

代码审查:

  • Localization accessor used consistently throughout the project
  • Date, time, number, and currency formatting is locale-aware
  • Text directionality (RTL) supported if targeting Arabic, Hebrew, etc.
  • No string concatenation for localized text — use parameterized messages

  • 项目中统一使用本地化访问器
  • 日期、时间、数字和货币格式支持区域设置
  • 若目标为阿拉伯语、希伯来语等,支持文本方向(RTL)
  • 本地化文本不使用字符串拼接——使用参数化消息

14. Dependency Injection

14. 依赖注入

Principles (apply to any DI approach):

原则(适用于任何DI方案):

  • Classes depend on abstractions (interfaces), not concrete implementations at layer boundaries
  • Dependencies provided externally via constructor, DI framework, or provider graph — not created internally
  • Registration distinguishes lifetime: singleton vs factory vs lazy singleton
  • Environment-specific bindings (dev/staging/prod) use configuration, not runtime
    if
    checks
  • No circular dependencies in the DI graph
  • Service locator calls (if used) are not scattered throughout business logic

  • 类依赖抽象(接口),而非层边界的具体实现
  • 依赖通过构造函数、DI框架或Provider图外部提供——而非内部创建
  • 注册时区分生命周期:单例、工厂、懒加载单例
  • 环境特定绑定(开发/预发布/生产)使用配置,而非运行时
    if
    检查
  • DI图中无循环依赖
  • 服务定位器调用(若使用)不分散在业务逻辑中

15. Static Analysis

15. 静态分析

Configuration:

配置:

  • analysis_options.yaml
    present with strict settings enabled
  • Strict analyzer settings:
    strict-casts: true
    ,
    strict-inference: true
    ,
    strict-raw-types: true
  • A comprehensive lint rule set is included (very_good_analysis, flutter_lints, or custom strict rules)
  • All sub-packages in monorepos inherit or share the root analysis options
  • 存在
    analysis_options.yaml
    并启用严格设置
  • 严格分析器设置:
    strict-casts: true
    strict-inference: true
    strict-raw-types: true
  • 包含全面的lint规则集(very_good_analysis、flutter_lints或自定义严格规则)
  • 单仓库中的所有子包继承或共享根目录的分析选项

Enforcement:

执行:

  • No unresolved analyzer warnings in committed code
  • Lint suppressions (
    // ignore:
    ) are justified with comments explaining why
  • flutter analyze
    runs in CI and failures block merges
  • 提交的代码中无未解决的分析器警告
  • Lint抑制(
    // ignore:
    )附带注释说明原因
  • flutter analyze
    在CI中运行,失败会阻止合并

Key rules to verify regardless of lint package:

无论lint包如何,都要验证的关键规则:

  • prefer_const_constructors
    — performance in widget trees
  • avoid_print
    — use proper logging
  • unawaited_futures
    — prevent fire-and-forget async bugs
  • prefer_final_locals
    — immutability at variable level
  • always_declare_return_types
    — explicit contracts
  • avoid_catches_without_on_clauses
    — specific error handling
  • always_use_package_imports
    — consistent import style

  • prefer_const_constructors
    ——Widget树性能
  • avoid_print
    ——使用正确的日志
  • unawaited_futures
    ——防止异步遗忘bug
  • prefer_final_locals
    ——变量级别的不可变性
  • always_declare_return_types
    ——显式契约
  • avoid_catches_without_on_clauses
    ——特定错误处理
  • always_use_package_imports
    ——统一导入风格

State Management Quick Reference

状态管理快速参考

The table below maps universal principles to their implementation in popular solutions. Use this to adapt review rules to whichever solution the project uses.
PrincipleBLoC/CubitRiverpodProviderGetXMobXSignalsBuilt-in
State container
Bloc
/
Cubit
Notifier
/
AsyncNotifier
ChangeNotifier
GetxController
Store
signal()
StatefulWidget
UI consumer
BlocBuilder
ConsumerWidget
Consumer
Obx
/
GetBuilder
Observer
Watch
setState
Selector
BlocSelector
/
buildWhen
ref.watch(p.select(...))
Selector
N/Acomputed
computed()
N/A
Side effects
BlocListener
ref.listen
Consumer
callback
ever()
/
once()
reaction
effect()
callbacks
Disposalauto via
BlocProvider
.autoDispose
auto via
Provider
onClose()
ReactionDisposer
manual
dispose()
Testing
blocTest()
ProviderContainer
ChangeNotifier
directly
Get.put
in test
store directlysignal directlywidget test

下表将通用原则映射到主流方案的实现。使用此表将审查规则适配到项目使用的方案。
原则BLoC/CubitRiverpodProviderGetXMobXSignals内置方案
状态容器
Bloc
/
Cubit
Notifier
/
AsyncNotifier
ChangeNotifier
GetxController
Store
signal()
StatefulWidget
UI消费者
BlocBuilder
ConsumerWidget
Consumer
Obx
/
GetBuilder
Observer
Watch
setState
选择器
BlocSelector
/
buildWhen
ref.watch(p.select(...))
Selector
N/Acomputed
computed()
N/A
副作用
BlocListener
ref.listen
Consumer
回调
ever()
/
once()
reaction
effect()
回调
销毁通过
BlocProvider
自动销毁
.autoDispose
通过
Provider
自动销毁
onClose()
ReactionDisposer
手动销毁
dispose()
测试
blocTest()
ProviderContainer
直接使用
ChangeNotifier
测试中使用
Get.put
直接使用store直接使用signalWidget测试

Sources

参考来源