flutter-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter Best Practices

Flutter 最佳实践

Expert guidelines for building production-ready Flutter applications across mobile, web, and desktop platforms.
面向移动、Web和桌面平台,构建可投入生产的Flutter应用的专家指南。

Quick Start

快速入门

When starting a new Flutter project or reviewing code:
  1. Architecture: Use feature-first folder structure with clean separation of concerns
  2. State Management: Prefer Riverpod for new projects; use BLoC for complex async flows
  3. Navigation: Use go_router for deep linking and web support
  4. Testing: Follow the testing pyramid - unit → widget → integration
  5. Performance: Use
    const
    constructors,
    ListView.builder
    , and proper disposal patterns
当启动新Flutter项目或审查代码时:
  1. 架构:采用以功能为优先的文件夹结构,清晰分离关注点
  2. 状态管理:新项目优先使用Riverpod;复杂异步流场景使用BLoC
  3. 导航:使用go_router实现深度链接与Web支持
  4. 测试:遵循测试金字塔 - 单元测试 → 组件测试 → 集成测试
  5. 性能:使用
    const
    构造函数、
    ListView.builder
    以及正确的资源销毁模式

Architecture Patterns

架构模式

Three-Layer Architecture

三层架构

Organize code into distinct layers for maintainability:
lib/
├── features/
│   └── feature_name/
│       ├── presentation/     # UI widgets, screens
│       ├── domain/           # Business logic, use cases, entities
│       └── data/             # Repositories, data sources, models
├── core/
│   ├── theme/               # AppTheme, colors, typography
│   ├── router/              # GoRouter configuration
│   └── utils/               # Shared utilities
└── main.dart
Principles:
  • Dependency Rule: Higher layers depend on lower layers only
  • Feature API Interface: Hide implementation details behind interfaces
  • Repository Pattern: Abstract data access for testability
将代码组织为不同层级以提升可维护性:
lib/
├── features/
│   └── feature_name/
│       ├── presentation/     # UI widgets, screens
│       ├── domain/           # Business logic, use cases, entities
│       └── data/             # Repositories, data sources, models
├── core/
│   ├── theme/               # AppTheme, colors, typography
│   ├── router/              # GoRouter configuration
│   └── utils/               # Shared utilities
└── main.dart
原则:
  • 依赖规则:高层级仅依赖低层级
  • 功能API接口:通过接口隐藏实现细节
  • 仓库模式:抽象数据访问以提升可测试性

Widget Architecture

Widget架构

Widget Types Decision Tree:
ScenarioUse
Static UI, no changing state
StatelessWidget
Interactive UI with local state
StatefulWidget
Data shared down the tree
InheritedWidget
/ Riverpod
Complex reusable UI componentCustom widget class
StatefulWidget Lifecycle (Critical):
dart
class _MyWidgetState extends State<MyWidget> {
  
  void initState() {
    super.initState();
    // One-time initialization (controllers, subscriptions)
  }
  
  
  void didChangeDependencies() {
    super.didChangeDependencies();
    // Called when InheritedWidget dependencies change
  }
  
  
  Widget build(BuildContext context) {
    // Keep fast and synchronous - no heavy computation
    return Container();
  }
  
  
  void dispose() {
    // CRITICAL: Dispose controllers, cancel subscriptions
    _controller.dispose();
    _subscription?.cancel();
    super.dispose();
  }
}
Golden Rule: Always dispose controllers, scroll controllers, text controllers, and cancel stream subscriptions.
Widget类型决策树:
场景使用
静态UI,无状态变化
StatelessWidget
带本地状态的交互式UI
StatefulWidget
数据在组件树中向下共享
InheritedWidget
/ Riverpod
复杂可复用UI组件自定义Widget类
StatefulWidget生命周期(关键):
dart
class _MyWidgetState extends State<MyWidget> {
  
  void initState() {
    super.initState();
    // One-time initialization (controllers, subscriptions)
  }
  
  
  void didChangeDependencies() {
    super.didChangeDependencies();
    // Called when InheritedWidget dependencies change
  }
  
  
  Widget build(BuildContext context) {
    // Keep fast and synchronous - no heavy computation
    return Container();
  }
  
  
  void dispose() {
    // CRITICAL: Dispose controllers, cancel subscriptions
    _controller.dispose();
    _subscription?.cancel();
    super.dispose();
  }
}
黄金法则:务必销毁控制器、滚动控制器、文本控制器,并取消流订阅。

State Management

状态管理

Riverpod (Recommended for New Projects)

Riverpod(新项目推荐)

Setup:
dart
// main.dart
void main() {
  runApp(ProviderScope(child: MyApp()));
}

// providers.dart
final counterProvider = StateProvider<int>((ref) => 0);

final userProvider = FutureProvider<User>((ref) async {
  return await ref.watch(authRepositoryProvider).getCurrentUser();
});


class TodoList extends _$TodoList {
  
  List<Todo> build() => [];
  
  void add(Todo todo) => state = [...state, todo];
  void toggle(String id) {
    state = state.map((t) => 
      t.id == id ? t.copyWith(completed: !t.completed) : t
    ).toList();
  }
}
Usage Patterns:
dart
class MyWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    // Watch for UI updates
    final count = ref.watch(counterProvider);
    final asyncUser = ref.watch(userProvider);
    final todos = ref.watch(todoListProvider);
    
    // Read for one-time access (callbacks)
    void onPressed() {
      ref.read(todoListProvider.notifier).add(newTodo);
    }
    
    return asyncUser.when(
      data: (user) => Text(user.name),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => ErrorWidget(err),
    );
  }
}
Key Methods:
  • ref.watch(provider)
    : Rebuild widget when provider changes
  • ref.read(provider)
    : Get value once (use in callbacks, not build)
  • ref.listen(provider)
    : Listen for changes and perform side effects
配置:
dart
// main.dart
void main() {
  runApp(ProviderScope(child: MyApp()));
}

// providers.dart
final counterProvider = StateProvider<int>((ref) => 0);

final userProvider = FutureProvider<User>((ref) async {
  return await ref.watch(authRepositoryProvider).getCurrentUser();
});


class TodoList extends _$TodoList {
  
  List<Todo> build() => [];
  
  void add(Todo todo) => state = [...state, todo];
  void toggle(String id) {
    state = state.map((t) => 
      t.id == id ? t.copyWith(completed: !t.completed) : t
    ).toList();
  }
}
使用模式:
dart
class MyWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    // Watch for UI updates
    final count = ref.watch(counterProvider);
    final asyncUser = ref.watch(userProvider);
    final todos = ref.watch(todoListProvider);
    
    // Read for one-time access (callbacks)
    void onPressed() {
      ref.read(todoListProvider.notifier).add(newTodo);
    }
    
    return asyncUser.when(
      data: (user) => Text(user.name),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => ErrorWidget(err),
    );
  }
}
核心方法:
  • ref.watch(provider)
    :当provider变化时重建组件
  • ref.read(provider)
    :一次性获取值(用于回调,不要在build中使用)
  • ref.listen(provider)
    :监听变化并执行副作用

BLoC Pattern (For Complex Async Flows)

BLoC模式(复杂异步流场景)

When to use BLoC:
  • Complex event-driven architectures
  • Apps with heavy business logic separation requirements
  • Teams familiar with reactive programming
dart
// Events
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}

// States
class CounterState {
  final int count;
  CounterState(this.count);
}

// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<CounterIncrementPressed>((event, emit) {
      emit(CounterState(state.count + 1));
    });
  }
}

// Usage
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('Count: ${state.count}');
  },
)
何时使用BLoC:
  • 复杂事件驱动架构
  • 对业务逻辑分离要求高的应用
  • 熟悉响应式编程的团队
dart
// Events
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}

// States
class CounterState {
  final int count;
  CounterState(this.count);
}

// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<CounterIncrementPressed>((event, emit) {
      emit(CounterState(state.count + 1));
    });
  }
}

// Usage
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('Count: ${state.count}');
  },
)

State Selection Guide

状态选择指南

Use CaseSolution
Simple local widget state
StatefulWidget
+
setState()
Single value shared across widgets
StateProvider
Async data (API calls)
FutureProvider
Stream-based data
StreamProvider
Complex state with business logic
StateNotifier
/ BLoC
Global app stateRiverpod with scoped providers
使用场景解决方案
简单本地组件状态
StatefulWidget
+
setState()
跨组件共享的单一值
StateProvider
异步数据(API调用)
FutureProvider
基于流的数据
StreamProvider
带业务逻辑的复杂状态
StateNotifier
/ BLoC
全局应用状态带作用域Provider的Riverpod

Navigation

导航

go_router (Recommended)

go_router(推荐)

Setup:
dart
final _router = GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    // Auth guard
    final isLoggedIn = authState.isAuthenticated;
    final isLoggingIn = state.matchedLocation == '/login';
    
    if (!isLoggedIn && !isLoggingIn) return '/login';
    if (isLoggedIn && isLoggingIn) return '/';
    return null;
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (context, state) => DetailScreen(
            id: state.pathParameters['id']!,
          ),
        ),
      ],
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => LoginScreen(),
    ),
  ],
);

// In MaterialApp
MaterialApp.router(routerConfig: _router);
Navigation:
dart
// Navigate
context.go('/details/123');
context.push('/details/123');  // Preserves navigation stack
context.pop();

// With query parameters
context.goNamed('details', queryParameters: {'tab': 'reviews'});
Deep Linking Setup:
Android (
AndroidManifest.xml
):
xml
<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <category android:name="android.intent.category.BROWSABLE"/>
  <data android:scheme="https" android:host="yourdomain.com"/>
</intent-filter>
iOS (
info.plist
):
xml
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourscheme</string>
    </array>
  </dict>
</array>
配置:
dart
final _router = GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    // Auth guard
    final isLoggedIn = authState.isAuthenticated;
    final isLoggingIn = state.matchedLocation == '/login';
    
    if (!isLoggedIn && !isLoggingIn) return '/login';
    if (isLoggedIn && isLoggingIn) return '/';
    return null;
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (context, state) => DetailScreen(
            id: state.pathParameters['id']!,
          ),
        ),
      ],
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => LoginScreen(),
    ),
  ],
);

// In MaterialApp
MaterialApp.router(routerConfig: _router);
导航操作:
dart
// 导航
context.go('/details/123');
context.push('/details/123');  // 保留导航栈
context.pop();

// 带查询参数
context.goNamed('details', queryParameters: {'tab': 'reviews'});
深度链接配置:
Android(
AndroidManifest.xml
):
xml
<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <category android:name="android.intent.category.BROWSABLE"/>
  <data android:scheme="https" android:host="yourdomain.com"/>
</intent-filter>
iOS(
info.plist
):
xml
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourscheme</string>
    </array>
  </dict>
</array>

Widget Patterns

Widget模式

Layout Essentials

布局基础

dart
// Common layout patterns
Column(           // Vertical layout
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [...],
)

Row(              // Horizontal layout
  children: [
    Expanded(flex: 2, child: ...),  // Takes 2/3 space
    Expanded(flex: 1, child: ...),  // Takes 1/3 space
  ],
)

Stack(            // Overlapping widgets
  children: [
    Positioned.fill(child: Background()),
    Align(alignment: Alignment.bottomCenter, child: ...),
  ],
)

// Responsive layouts
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return DesktopLayout();
    }
    return MobileLayout();
  },
)
dart
// 常见布局模式
Column(           // 垂直布局
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [...],
)

Row(              // 水平布局
  children: [
    Expanded(flex: 2, child: ...),  // 占2/3空间
    Expanded(flex: 1, child: ...),  // 占1/3空间
  ],
)

Stack(            // 重叠组件
  children: [
    Positioned.fill(child: Background()),
    Align(alignment: Alignment.bottomCenter, child: ...),
  ],
)

// 响应式布局
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return DesktopLayout();
    }
    return MobileLayout();
  },
)

List Optimization

列表优化

ALWAYS use builder for large/infinite lists:
dart
// Good - lazy loading, only builds visible items
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(
    title: Text(items[index].title),
  ),
)

// For grids with images
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: crossAxisCount,
  ),
  itemCount: items.length,
  itemBuilder: (context, index) => ImageCard(items[index]),
)

// With custom scroll effects
CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200,
      flexibleSpace: FlexibleSpaceBar(title: Text('Title')),
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) => ListTile(...),
        childCount: items.length,
      ),
    ),
  ],
)
大型/无限列表务必使用builder:
dart
// 推荐 - 懒加载,仅构建可见项
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(
    title: Text(items[index].title),
  ),
)

// 带图片的网格
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: crossAxisCount,
  ),
  itemCount: items.length,
  itemBuilder: (context, index) => ImageCard(items[index]),
)

// 自定义滚动效果
CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200,
      flexibleSpace: FlexibleSpaceBar(title: Text('Title')),
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) => ListTile(...),
        childCount: items.length,
      ),
    ),
  ],
)

Form Handling

表单处理

dart
class _MyFormState extends State<MyForm> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  
  
  void dispose() {
    _nameController.dispose();
    super.dispose();
  }
  
  
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _nameController,
            decoration: InputDecoration(labelText: 'Name'),
            validator: (value) {
              if (value?.isEmpty ?? true) return 'Required';
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                _formKey.currentState!.save();
                // Process form
              }
            },
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}
dart
class _MyFormState extends State<MyForm> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  
  
  void dispose() {
    _nameController.dispose();
    super.dispose();
  }
  
  
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _nameController,
            decoration: InputDecoration(labelText: 'Name'),
            validator: (value) {
              if (value?.isEmpty ?? true) return 'Required';
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                _formKey.currentState!.save();
                // Process form
              }
            },
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}

Performance Optimization

性能优化

Critical Performance Rules

关键性能规则

RuleImplementationPriority
Use
const
constructors
const Text('Hello')
Critical
Use builders for lists
ListView.builder
Critical
Dispose controllers
controller.dispose()
Critical
Cache expensive operations
compute()
for heavy work
High
Use
RepaintBoundary
Wrap animated widgetsMedium
Avoid clippingMinimize
Clip
widgets
Medium
规则实现方式优先级
使用
const
构造函数
const Text('Hello')
关键
列表使用builder
ListView.builder
关键
销毁控制器
controller.dispose()
关键
缓存昂贵操作使用
compute()
处理繁重任务
使用
RepaintBoundary
包裹动画组件
避免裁剪减少
Clip
组件使用

Memory Management

内存管理

dart
// Always check mounted before setState in async
void _loadData() async {
  final data = await fetchData();
  if (mounted) {  // CRITICAL
    setState(() => _data = data);
  }
}

// Limit image cache for memory-constrained apps
PaintingBinding.instance.imageCache.maximumSize = 100;
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 * 1024 * 1024;

// Use weak references for long-lived callbacks
final weakRef = WeakReference(object);
dart
// 异步操作中调用setState前务必检查mounted
void _loadData() async {
  final data = await fetchData();
  if (mounted) {  // 关键
    setState(() => _data = data);
  }
}

// 对内存受限应用限制图片缓存
PaintingBinding.instance.imageCache.maximumSize = 100;
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 * 1024 * 1024;

// 长期回调使用弱引用
final weakRef = WeakReference(object);

Isolate Usage

隔离区使用

Offload heavy computation from UI thread:
dart
// Heavy computation
final result = await compute(parseJson, largeJsonString);

// For complex parsing
Future<List<Model>> parseModels(String jsonString) async {
  return compute((str) {
    final decoded = json.decode(str) as List;
    return decoded.map((e) => Model.fromJson(e)).toList();
  }, jsonString);
}
将繁重计算从UI线程卸载:
dart
// 繁重计算
final result = await compute(parseJson, largeJsonString);

// 复杂解析
Future<List<Model>> parseModels(String jsonString) async {
  return compute((str) {
    final decoded = json.decode(str) as List;
    return decoded.map((e) => Model.fromJson(e)).toList();
  }, jsonString);
}

DevTools Profiling

DevTools性能分析

Enable these flags for debugging:
dart
// In main.dart for debugging
void main() {
  // Visualize widget rebuilds
  debugRepaintRainbowEnabled = true;
  
  // Show paint bounds
  debugPaintSizeEnabled = true;
  
  runApp(MyApp());
}
Key DevTools Views:
  • Widget Rebuild Counts: Identify excessive rebuilds
  • Performance: Frame timing, shader compilation
  • Memory: Heap snapshots, allocation tracking
  • Network: API call monitoring
启用以下标记用于调试:
dart
// main.dart中用于调试
void main() {
  // 可视化组件重建
  debugRepaintRainbowEnabled = true;
  
  // 显示绘制边界
  debugPaintSizeEnabled = true;
  
  runApp(MyApp());
}
DevTools核心视图:
  • 组件重建次数:识别过度重建问题
  • 性能:帧时序、着色器编译
  • 内存:堆快照、分配跟踪
  • 网络:API调用监控

Testing

测试

Testing Pyramid

测试金字塔

       ▲ Integration (Full flows)
      ╱ ╲
     ╱   ╲ Widget (UI components)
    ╱     ╲
   ╱       ╲
  ╱_________╲
 Unit (Logic, pure Dart)
       ▲ 集成测试(完整流程)
      ╱ ╲
     ╱   ╲ 组件测试(UI组件)
    ╱     ╲
   ╱       ╲
  ╱_________╲
 单元测试(逻辑、纯Dart代码)

Unit Testing

单元测试

dart
test('can calculate total price', () {
  // Arrange
  final cart = Cart(items: [
    Item(price: 10, quantity: 2),
    Item(price: 5, quantity: 1),
  ]);
  
  // Act
  final total = cart.total;
  
  // Assert
  expect(total, equals(25));
});

// With Riverpod
test('counter increments', () {
  final container = ProviderContainer();
  addTearDown(container.dispose);
  
  final notifier = container.read(counterProvider.notifier);
  notifier.state = 5;
  notifier.state++;
  
  expect(container.read(counterProvider), equals(6));
});
dart
test('can calculate total price', () {
  // Arrange
  final cart = Cart(items: [
    Item(price: 10, quantity: 2),
    Item(price: 5, quantity: 1),
  ]);
  
  // Act
  final total = cart.total;
  
  // Assert
  expect(total, equals(25));
});

// 结合Riverpod测试
test('counter increments', () {
  final container = ProviderContainer();
  addTearDown(container.dispose);
  
  final notifier = container.read(counterProvider.notifier);
  notifier.state = 5;
  notifier.state++;
  
  expect(container.read(counterProvider), equals(6));
});

Widget Testing

组件测试

dart
testWidgets('displays user name', (WidgetTester tester) async {
  // Arrange
  await tester.pumpWidget(
    MaterialApp(
      home: UserProfile(user: User(name: 'John')),
    ),
  );
  
  // Act & Assert
  expect(find.text('John'), findsOneWidget);
  expect(find.byType(CircularProgressIndicator), findsNothing);
});

testWidgets('tapping button increments counter', (tester) async {
  await tester.pumpWidget(MyApp());
  
  await tester.tap(find.byIcon(Icons.add));
  await tester.pumpAndSettle();
  
  expect(find.text('1'), findsOneWidget);
});
dart
testWidgets('displays user name', (WidgetTester tester) async {
  // Arrange
  await tester.pumpWidget(
    MaterialApp(
      home: UserProfile(user: User(name: 'John')),
    ),
  );
  
  // Act & Assert
  expect(find.text('John'), findsOneWidget);
  expect(find.byType(CircularProgressIndicator), findsNothing);
});

testWidgets('tapping button increments counter', (tester) async {
  await tester.pumpWidget(MyApp());
  
  await tester.tap(find.byIcon(Icons.add));
  await tester.pumpAndSettle();
  
  expect(find.text('1'), findsOneWidget);
});

Golden Tests (Visual Regression)

黄金测试(视觉回归)

dart
testGoldens('UserProfile renders correctly', (tester) async {
  final builder = GoldenBuilder.grid(columns: 2)
    ..addScenario('Light', UserProfile(user: mockUser))
    ..addScenario('Dark', Theme(data: darkTheme, child: UserProfile(user: mockUser)));
  
  await tester.pumpWidgetBuilder(builder.build());
  await screenMatchesGolden(tester, 'user_profile');
});

// Update goldens: flutter test --update-goldens --tags=golden
dart
testGoldens('UserProfile renders correctly', (tester) async {
  final builder = GoldenBuilder.grid(columns: 2)
    ..addScenario('Light', UserProfile(user: mockUser))
    ..addScenario('Dark', Theme(data: darkTheme, child: UserProfile(user: mockUser)));
  
  await tester.pumpWidgetBuilder(builder.build());
  await screenMatchesGolden(tester, 'user_profile');
});

// 更新黄金文件: flutter test --update-goldens --tags=golden

Integration Testing

集成测试

dart
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('full login flow', (tester) async {
    app.main();
    await tester.pumpAndSettle();
    
    await tester.enterText(find.byType(TextField).first, 'user@example.com');
    await tester.enterText(find.byType(TextField).last, 'password');
    await tester.tap(find.byType(ElevatedButton));
    await tester.pumpAndSettle();
    
    expect(find.text('Welcome'), findsOneWidget);
  });
}
dart
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('full login flow', (tester) async {
    app.main();
    await tester.pumpAndSettle();
    
    await tester.enterText(find.byType(TextField).first, 'user@example.com');
    await tester.enterText(find.byType(TextField).last, 'password');
    await tester.tap(find.byType(ElevatedButton));
    await tester.pumpAndSettle();
    
    expect(find.text('Welcome'), findsOneWidget);
  });
}

Networking

网络请求

HTTP Service Pattern

HTTP服务模式

dart
class ApiService {
  final Dio _dio;
  
  ApiService({Dio? dio}) : _dio = dio ?? Dio(BaseOptions(
    baseUrl: 'https://api.example.com',
    connectTimeout: Duration(seconds: 5),
    receiveTimeout: Duration(seconds: 3),
  )) {
    _dio.interceptors.add(AuthInterceptor());
    _dio.interceptors.add(LogInterceptor());
  }
  
  Future<List<User>> getUsers() async {
    final response = await _dio.get('/users');
    return (response.data as List)
      .map((json) => User.fromJson(json))
      .toList();
  }
}

// With Riverpod
final apiServiceProvider = Provider<ApiService>((ref) {
  return ApiService();
});

final usersProvider = FutureProvider<List<User>>((ref) async {
  final api = ref.watch(apiServiceProvider);
  return api.getUsers();
});
dart
class ApiService {
  final Dio _dio;
  
  ApiService({Dio? dio}) : _dio = dio ?? Dio(BaseOptions(
    baseUrl: 'https://api.example.com',
    connectTimeout: Duration(seconds: 5),
    receiveTimeout: Duration(seconds: 3),
  )) {
    _dio.interceptors.add(AuthInterceptor());
    _dio.interceptors.add(LogInterceptor());
  }
  
  Future<List<User>> getUsers() async {
    final response = await _dio.get('/users');
    return (response.data as List)
      .map((json) => User.fromJson(json))
      .toList();
  }
}

// 结合Riverpod
final apiServiceProvider = Provider<ApiService>((ref) {
  return ApiService();
});

final usersProvider = FutureProvider<List<User>>((ref) async {
  final api = ref.watch(apiServiceProvider);
  return api.getUsers();
});

JSON Serialization

JSON序列化

dart
// With freezed (recommended)

class User with _$User {
  const factory User({
    required String id,
    required String name,
    ([]) List<String> tags,
    DateTime? createdAt,
  }) = _User;
  
  factory User.fromJson(Map<String, dynamic> json) => 
      _$UserFromJson(json);
}

// Manual approach
class User {
  final String id;
  final String name;
  
  User({required this.id, required this.name});
  
  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'] as String,
    name: json['name'] as String,
  );
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
  };
}
dart
// 使用freezed(推荐)

class User with _$User {
  const factory User({
    required String id,
    required String name,
    ([]) List<String> tags,
    DateTime? createdAt,
  }) = _User;
  
  factory User.fromJson(Map<String, dynamic> json) => 
      _$UserFromJson(json);
}

// 手动实现
class User {
  final String id;
  final String name;
  
  User({required this.id, required this.name});
  
  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'] as String,
    name: json['name'] as String,
  );
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
  };
}

Firebase Integration

Firebase集成

Firebase Setup

Firebase配置

bash
undefined
bash
undefined

Install FlutterFire CLI

安装FlutterFire CLI

dart pub global activate flutterfire_cli
dart pub global activate flutterfire_cli

Configure project

配置项目

flutterfire configure --project=your-project-id
undefined
flutterfire configure --project=your-project-id
undefined

Cloud Messaging (Push Notifications)

云消息推送(推送通知)

dart
class NotificationService {
  final FirebaseMessaging _messaging;
  
  NotificationService(this._messaging);
  
  Future<void> initialize() async {
    // Request permissions
    final settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );
    
    // Get FCM token
    final token = await _messaging.getToken();
    await _saveToken(token);
    
    // Listen to token refresh
    _messaging.onTokenRefresh.listen(_saveToken);
    
    // Handle foreground messages
    FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
    
    // Handle background/terminated
    FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);
    final initialMessage = await _messaging.getInitialMessage();
    if (initialMessage != null) {
      _handleNotificationTap(initialMessage);
    }
  }
  
  ('vm:entry-point')
  static Future<void> _firebaseMessagingBackgroundHandler(
    RemoteMessage message,
  ) async {
    await Firebase.initializeApp();
    // Handle background message
  }
}

// Register in main
FirebaseMessaging.onBackgroundMessage(
  NotificationService._firebaseMessagingBackgroundHandler,
);
dart
class NotificationService {
  final FirebaseMessaging _messaging;
  
  NotificationService(this._messaging);
  
  Future<void> initialize() async {
    // 请求权限
    final settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );
    
    // 获取FCM令牌
    final token = await _messaging.getToken();
    await _saveToken(token);
    
    // 监听令牌刷新
    _messaging.onTokenRefresh.listen(_saveToken);
    
    // 处理前台消息
    FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
    
    // 处理后台/终止状态消息
    FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);
    final initialMessage = await _messaging.getInitialMessage();
    if (initialMessage != null) {
      _handleNotificationTap(initialMessage);
    }
  }
  
  ('vm:entry-point')
  static Future<void> _firebaseMessagingBackgroundHandler(
    RemoteMessage message,
  ) async {
    await Firebase.initializeApp();
    // Handle background message
  }
}

// 在main中注册
FirebaseMessaging.onBackgroundMessage(
  NotificationService._firebaseMessagingBackgroundHandler,
);

Cross-Platform Considerations

跨平台注意事项

Platform Detection

平台检测

dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;

bool get isWeb => kIsWeb;
bool get isMobile => !kIsWeb && (Platform.isIOS || Platform.isAndroid);
bool get isDesktop => !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux);

// Conditional UI
if (Platform.isIOS) {
  return CupertinoButton(child: Text('Done'), onPressed: () {});
}
return ElevatedButton(child: Text('Done'), onPressed: () {});
dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;

bool get isWeb => kIsWeb;
bool get isMobile => !kIsWeb && (Platform.isIOS || Platform.isAndroid);
bool get isDesktop => !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux);

// 条件UI
if (Platform.isIOS) {
  return CupertinoButton(child: Text('Done'), onPressed: () {});
}
return ElevatedButton(child: Text('Done'), onPressed: () {});

Adaptive Layouts

自适应布局

dart
// Using flutter_adaptive_scaffold
AdaptiveLayout(
  primaryNavigation: SlotLayout(config: {
    Breakpoints.mediumAndUp: SlotLayout.from(
      builder: (_) => NavigationRail(destinations: [...]),
    ),
  }),
  bottomNavigation: SlotLayout(config: {
    Breakpoints.small: SlotLayout.from(
      builder: (_) => BottomNavigationBar(items: [...]),
    ),
  }),
  body: SlotLayout(config: {
    Breakpoints.small: SlotLayout.from(builder: (_) => MobileBody()),
    Breakpoints.mediumAndUp: SlotLayout.from(builder: (_) => DesktopBody()),
  }),
)
dart
// 使用flutter_adaptive_scaffold
AdaptiveLayout(
  primaryNavigation: SlotLayout(config: {
    Breakpoints.mediumAndUp: SlotLayout.from(
      builder: (_) => NavigationRail(destinations: [...]),
    ),
  }),
  bottomNavigation: SlotLayout(config: {
    Breakpoints.small: SlotLayout.from(
      builder: (_) => BottomNavigationBar(items: [...]),
    ),
  }),
  body: SlotLayout(config: {
    Breakpoints.small: SlotLayout.from(builder: (_) => MobileBody()),
    Breakpoints.mediumAndUp: SlotLayout.from(builder: (_) => DesktopBody()),
  }),
)

Platform Channels

平台通道

Flutter side:
dart
class PlatformChannelService {
  static const MethodChannel _channel = 
      MethodChannel('com.example.app/channel');
  
  Future<String?> getNativeData() async {
    try {
      final result = await _channel.invokeMethod<String>('getNativeData');
      return result;
    } on PlatformException catch (e) {
      print('Error: ${e.message}');
      return null;
    }
  }
}
Flutter端:
dart
class PlatformChannelService {
  static const MethodChannel _channel = 
      MethodChannel('com.example.app/channel');
  
  Future<String?> getNativeData() async {
    try {
      final result = await _channel.invokeMethod<String>('getNativeData');
      return result;
    } on PlatformException catch (e) {
      print('Error: ${e.message}');
      return null;
    }
  }
}

Animations

动画

Implicit Animations

隐式动画

dart
// Simple animations that trigger on value change
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: isExpanded ? 200 : 100,
  height: isExpanded ? 200 : 100,
  color: isExpanded ? Colors.red : Colors.blue,
  child: child,
)

AnimatedOpacity(
  duration: Duration(milliseconds: 200),
  opacity: isVisible ? 1.0 : 0.0,
  child: child,
)
dart
// 值变化时触发的简单动画
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: isExpanded ? 200 : 100,
  height: isExpanded ? 200 : 100,
  color: isExpanded ? Colors.red : Colors.blue,
  child: child,
)

AnimatedOpacity(
  duration: Duration(milliseconds: 200),
  opacity: isVisible ? 1.0 : 0.0,
  child: child,
)

Explicit Animations

显式动画

dart
class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    _controller.forward();
  }
  
  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: ScaleTransition(
        scale: _animation,
        child: child,
      ),
    );
  }
}
dart
class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    _controller.forward();
  }
  
  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: ScaleTransition(
        scale: _animation,
        child: child,
      ),
    );
  }
}

Hero Animations

Hero动画

dart
// Source page
Hero(
  tag: 'image-${item.id}',
  child: Image.network(item.imageUrl),
)

// Destination page (same tag)
Hero(
  tag: 'image-${item.id}',
  child: Image.network(item.imageUrl),
)
dart
// 源页面
Hero(
  tag: 'image-${item.id}',
  child: Image.network(item.imageUrl),
)

// 目标页面(相同tag)
Hero(
  tag: 'image-${item.id}',
  child: Image.network(item.imageUrl),
)

Production Deployment

生产环境部署

Build Commands

构建命令

bash
undefined
bash
undefined

Android App Bundle (recommended for Play Store)

Android App Bundle(Play商店推荐)

flutter build appbundle --release --obfuscate --split-debug-info=symbols/
flutter build appbundle --release --obfuscate --split-debug-info=symbols/

Android APK

Android APK

flutter build apk --release --split-per-abi
flutter build apk --release --split-per-abi

iOS

iOS

flutter build ios --release
flutter build ios --release

Web

Web

flutter build web --release
flutter build web --release

Desktop

桌面端

flutter build windows --release flutter build macos --release flutter build linux --release
undefined
flutter build windows --release flutter build macos --release flutter build linux --release
undefined

Code Obfuscation

代码混淆

Always obfuscate for production:
bash
flutter build appbundle \
  --obfuscate \
  --split-debug-info=symbols/
Store symbol files for crash symbolication.
生产环境务必混淆:
bash
flutter build appbundle \
  --obfuscate \
  --split-debug-info=symbols/
保存符号文件用于崩溃符号解析。

CI/CD Pipeline (GitHub Actions Example)

CI/CD流水线(GitHub Actions示例)

yaml
name: Flutter CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'
      
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test
      - run: flutter build apk --release
yaml
name: Flutter CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'
      
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test
      - run: flutter build apk --release

References

参考资料

For detailed guides on specific topics:
  • State Management Deep Dive: See references/state-management.md
  • Testing Patterns: See references/testing.md
  • Performance Optimization: See references/performance.md
  • Platform Integration: See references/platform-channels.md
如需特定主题的详细指南:
  • 状态管理深度解析:参见 references/state-management.md
  • 测试模式:参见 references/testing.md
  • 性能优化:参见 references/performance.md
  • 平台集成:参见 references/platform-channels.md