Appearance
Flutter最佳实践
在Flutter开发过程中,遵循最佳实践可以帮助开发者构建高质量、可维护、高性能的应用程序。本章将详细介绍Flutter开发中的各种最佳实践,包括架构设计、代码组织、性能优化、测试策略等方面。
项目结构
推荐的项目结构
lib/
├── main.dart # 应用入口
├── app.dart # 应用根组件
├── config/ # 配置相关
│ ├── environment.dart
│ ├── routes.dart
│ └── themes.dart
├── core/ # 核心功能
│ ├── services/
│ │ ├── api_service.dart
│ │ ├── auth_service.dart
│ │ └── storage_service.dart
│ ├── utils/
│ │ ├── constants.dart
│ │ ├── extensions.dart
│ │ └── validators.dart
│ └── error/
│ ├── exceptions.dart
│ └── failure.dart
├── features/ # 特性模块
│ ├── auth/
│ │ ├── data/
│ │ │ ├── datasources/
│ │ │ ├── models/
│ │ │ └── repositories/
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ └── repositories/
│ │ └── presentation/
│ │ ├── bloc/
│ │ ├── pages/
│ │ └── widgets/
│ └── home/
│ ├── data/
│ ├── domain/
│ └── presentation/
├── shared/ # 共享资源
│ ├── widgets/
│ ├── models/
│ └── constants/
└── injection_container.dart # 依赖注入容器
特性模块结构(Clean Architecture)
dart
// features/user/data/models/user_model.dart
import 'package:json_annotation/json_annotation.dart';
import '../../domain/entities/user.dart';
part 'user_model.g.dart';
@JsonSerializable()
class UserModel extends User {
const UserModel({
required super.id,
required super.name,
required super.email,
});
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
Map<String, dynamic> toJson() => _$UserModelToJson(this);
User toEntity() {
return User(
id: id,
name: name,
email: email,
);
}
static UserModel fromEntity(User entity) {
return UserModel(
id: entity.id,
name: entity.name,
email: entity.email,
);
}
}
// features/user/domain/entities/user.dart
class User {
final String id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
}
// features/user/domain/usecases/get_user.dart
import 'package:fpdart/fpdart.dart';
import '../../../../core/error/failure.dart';
import '../repositories/user_repository.dart';
import '../entities/user.dart';
class GetUser {
final UserRepository repository;
GetUser(this.repository);
Future<Either<Failure, User>> call(String userId) async {
return await repository.getUser(userId);
}
}
// features/user/presentation/pages/user_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/user_bloc.dart';
import '../widgets/user_details.dart';
class UserPage extends StatelessWidget {
final String userId;
const UserPage({Key? key, required this.userId}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User Details')),
body: BlocProvider(
create: (context) => UserBloc(
getUser: context.read<GetUser>(),
)..add(LoadUser(userId)),
child: UserPageContent(),
),
);
}
}
class UserPageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
return switch (state) {
UserInitial() => Center(child: Text('Load user data')),
UserLoading() => Center(child: CircularProgressIndicator()),
UserLoaded(:final user) => UserDetails(user: user),
UserError(:final message) => Center(child: Text(message)),
};
},
);
}
}
状态管理最佳实践
BLoC模式
dart
// 使用Equatable简化状态比较
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
abstract class UserState extends Equatable {
const UserState();
@override
List<Object> get props => [];
}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
@override
List<Object> get props => [user];
}
class UserError extends UserState {
final String message;
const UserError(this.message);
@override
List<Object> get props => [message];
}
// 事件类
abstract class UserEvent extends Equatable {
const UserEvent();
@override
List<Object> get props => [];
}
class LoadUser extends UserEvent {
final String userId;
const LoadUser(this.userId);
@override
List<Object> get props => [userId];
}
// BLoC类
class UserBloc extends Bloc<UserEvent, UserState> {
final GetUser getUser;
UserBloc({required this.getUser}) : super(UserInitial()) {
on<LoadUser>(_onLoadUser);
}
Future<void> _onLoadUser(LoadUser event, Emitter<UserState> emit) async {
emit(UserLoading());
final result = await getUser(event.userId);
result.fold(
(failure) => emit(UserError(failure.message)),
(user) => emit(UserLoaded(user)),
);
}
}
Riverpod状态管理
dart
// providers/user_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/usecases/get_user.dart';
import '../models/user.dart';
// 异步提供器
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
final getUser = ref.watch(getUserUseCaseProvider);
final result = await getUser(userId);
return result.fold(
(failure) => throw Exception(failure.message),
(user) => user,
);
});
// 状态提供器
final userStateProvider = StateNotifierProvider<UserNotifier, User?>((ref) {
return UserNotifier();
});
class UserNotifier extends StateNotifier<User?> {
UserNotifier() : super(null);
void setUser(User user) {
state = user;
}
void clearUser() {
state = null;
}
}
// Repository提供器
final userRepositoryProvider = Provider<UserRepository>((ref) {
return UserRepositoryImpl(
remoteDataSource: ref.watch(remoteDataSourceProvider),
localDataSource: ref.watch(localDataSourceProvider),
);
});
UI组件最佳实践
可重用组件
dart
// shared/widgets/custom_button.dart
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool isLoading;
final ButtonStyle? style;
final EdgeInsetsGeometry? padding;
const CustomButton({
Key? key,
required this.text,
this.onPressed,
this.isLoading = false,
this.style,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: style ??
ElevatedButton.styleFrom(
padding: padding ?? EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: isLoading
? SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.onPrimary,
),
),
)
: Text(
text,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
);
}
}
// shared/widgets/loading_widget.dart
class LoadingWidget extends StatelessWidget {
final String? message;
const LoadingWidget({Key? key, this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
if (message != null) ...[
SizedBox(height: 16),
Text(message!),
],
],
),
);
}
}
// shared/widgets/error_widget.dart
class ErrorWidget extends StatelessWidget {
final String message;
final VoidCallback? onRetry;
const ErrorWidget({
Key? key,
required this.message,
this.onRetry,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Theme.of(context).colorScheme.error,
),
SizedBox(height: 16),
Text(
message,
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
if (onRetry != null) ...[
SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: Text('Retry'),
),
],
],
),
);
}
}
响应式设计
dart
// shared/utils/responsive.dart
import 'package:flutter/material.dart';
class ResponsiveUtil {
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < 600;
}
static bool isTablet(BuildContext context) {
return MediaQuery.of(context).size.width >= 600 &&
MediaQuery.of(context).size.width < 900;
}
static bool isDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= 900;
}
static double getResponsivePadding(BuildContext context) {
if (isMobile(context)) {
return 16.0;
} else if (isTablet(context)) {
return 24.0;
} else {
return 32.0;
}
}
}
// 使用示例
class ResponsiveScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(ResponsiveUtil.getResponsivePadding(context)),
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
// 移动端布局
return MobileLayout();
} else if (constraints.maxWidth < 900) {
// 平板布局
return TabletLayout();
} else {
// 桌面布局
return DesktopLayout();
}
},
),
),
);
}
}
class MobileLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(height: 200, color: Colors.blue),
Expanded(child: Container(color: Colors.grey[200])),
],
);
}
}
class DesktopLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: [
Container(width: 250, color: Colors.grey[300]),
Expanded(
child: Column(
children: [
Container(height: 200, color: Colors.blue),
Expanded(child: Container(color: Colors.grey[200])),
],
),
),
],
);
}
}
代码组织最佳实践
常量管理
dart
// shared/constants/app_constants.dart
class AppConstants {
// API相关
static const String baseUrl = String.fromEnvironment('BASE_URL', defaultValue: 'https://api.example.com');
static const Duration apiTimeout = Duration(seconds: 30);
// 存储键名
static const String tokenKey = 'auth_token';
static const String userPreferencesKey = 'user_preferences';
static const String themeModeKey = 'theme_mode';
// 尺寸相关
static const double smallPadding = 8.0;
static const double mediumPadding = 16.0;
static const double largePadding = 24.0;
static const double smallRadius = 4.0;
static const double mediumRadius = 8.0;
static const double largeRadius = 16.0;
// 字体大小
static const double smallFontSize = 12.0;
static const double mediumFontSize = 16.0;
static const double largeFontSize = 20.0;
// 动画时长
static const Duration shortAnimation = Duration(milliseconds: 200);
static const Duration mediumAnimation = Duration(milliseconds: 300);
static const Duration longAnimation = Duration(milliseconds: 500);
}
// shared/constants/assets_constants.dart
class AssetConstants {
// 图片资源
static const String logo = 'assets/images/logo.png';
static const String splashBg = 'assets/images/splash_bg.png';
// 图标资源
static const String icHome = 'assets/icons/home.svg';
static const String icProfile = 'assets/icons/profile.svg';
static const String icSettings = 'assets/icons/settings.svg';
// JSON资源
static const String sampleData = 'assets/data/sample.json';
}
// shared/constants/validation_constants.dart
class ValidationConstants {
static const String emailRegex = r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$';
static const int minPasswordLength = 8;
static const int maxPasswordLength = 128;
static const String passwordPattern = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$';
}
扩展方法
dart
// shared/extensions/string_extensions.dart
extension StringExtensions on String {
bool isValidEmail() {
final RegExp emailExp = RegExp(ValidationConstants.emailRegex);
return emailExp.hasMatch(this);
}
bool isValidPassword() {
if (length < ValidationConstants.minPasswordLength) return false;
if (length > ValidationConstants.maxPasswordLength) return false;
final RegExp pwdExp = RegExp(ValidationConstants.passwordPattern);
return pwdExp.hasMatch(this);
}
String capitalize() {
if (isEmpty) return this;
return this[0].toUpperCase() + substring(1).toLowerCase();
}
String truncate(int maxLength) {
if (length <= maxLength) return this;
return substring(0, maxLength) + '...';
}
}
// shared/extensions/context_extensions.dart
import 'package:flutter/material.dart';
extension ContextExtensions on BuildContext {
// 尺寸相关
double get screenWidth => MediaQuery.sizeOf(this).width;
double get screenHeight => MediaQuery.sizeOf(this).height;
double get statusBarHeight => MediaQuery.paddingOf(this).top;
double get bottomPadding => MediaQuery.paddingOf(this).bottom;
// 主题相关
ThemeData get theme => Theme.of(this);
TextTheme get textTheme => Theme.of(this).textTheme;
ColorScheme get colorScheme => Theme.of(this).colorScheme;
// 导航相关
void pop<T extends Object>({T? result}) => Navigator.pop(this, result);
Future<T?> pushNamed<T extends Object>(String routeName, {Object? arguments}) =>
Navigator.pushNamed<T>(this, routeName, arguments: arguments);
void pushReplacementNamed(String routeName, {Object? arguments}) =>
Navigator.pushReplacementNamed(this, routeName, arguments: arguments);
// 便捷方法
void showSnackBar(String message, {Color? backgroundColor}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: backgroundColor,
),
);
}
}
工具类
dart
// shared/utils/date_time_utils.dart
class DateTimeUtils {
static String formatToDisplay(DateTime dateTime) {
return "${dateTime.day}/${dateTime.month}/${dateTime.year}";
}
static String formatToTime(DateTime dateTime) {
return "${dateTime.hour}:${dateTime.minute.toString().padLeft(2, '0')}";
}
static String formatToDateTime(DateTime dateTime) {
return "${formatToDisplay(dateTime)} ${formatToTime(dateTime)}";
}
static String getTimeAgo(DateTime dateTime) {
final now = DateTime.now();
final difference = now.difference(dateTime);
if (difference.inDays > 0) {
return '${difference.inDays}天前';
} else if (difference.inHours > 0) {
return '${difference.inHours}小时前';
} else if (difference.inMinutes > 0) {
return '${difference.inMinutes}分钟前';
} else {
return '刚刚';
}
}
static bool isToday(DateTime dateTime) {
final today = DateTime.now();
return dateTime.year == today.year &&
dateTime.month == today.month &&
dateTime.day == today.day;
}
}
// shared/utils/validator_utils.dart
class ValidatorUtils {
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return '请输入邮箱地址';
}
if (!value.isValidEmail()) {
return '请输入有效的邮箱地址';
}
return null;
}
static String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (!value.isValidPassword()) {
return '密码必须至少8位,包含大小写字母和数字';
}
return null;
}
static String? validateRequired(String? value, {String fieldName = '此字段'}) {
if (value == null || value.trim().isEmpty) {
return '$fieldName不能为空';
}
return null;
}
static String? validateMinLength(String? value, int minLength, {String fieldName = '此字段'}) {
if (value == null || value.length < minLength) {
return '$fieldName至少需要$minLength个字符';
}
return null;
}
}
错误处理最佳实践
统一错误处理
dart
// core/error/failure.dart
abstract class Failure {
final String message;
const Failure(this.message);
}
class ServerFailure extends Failure {
const ServerFailure(super.message);
}
class CacheFailure extends Failure {
const CacheFailure(super.message);
}
class NetworkFailure extends Failure {
const NetworkFailure(super.message);
}
// core/error/exceptions.dart
class ServerException implements Exception {
final String message;
ServerException(this.message);
}
class CacheException implements Exception {
final String message;
CacheException(this.message);
}
// core/error/error_handler.dart
import 'package:dio/dio.dart';
class ErrorHandler {
static Failure handle(Object error) {
if (error is DioException) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return const NetworkFailure('网络连接超时');
case DioExceptionType.badResponse:
switch (error.response?.statusCode) {
case 400:
return const ServerFailure('请求参数错误');
case 401:
return const ServerFailure('未授权访问');
case 403:
return const ServerFailure('禁止访问');
case 404:
return const ServerFailure('请求资源不存在');
case 500:
default:
return const ServerFailure('服务器内部错误');
}
case DioExceptionType.cancel:
return const NetworkFailure('请求被取消');
case DioExceptionType.connectionError:
case DioExceptionType.badCertificate:
case DioExceptionType.unknown:
default:
return const NetworkFailure('网络连接错误');
}
} else if (error is ServerException) {
return ServerFailure(error.message);
} else if (error is CacheException) {
return CacheFailure(error.message);
} else {
return const ServerFailure('未知错误');
}
}
}
全局错误处理
dart
// main.dart
import 'package:flutter/foundation.dart';
void main() {
// 设置全局错误处理
FlutterError.onError = (FlutterErrorDetails details) {
// 记录错误
if (kDebugMode) {
print(details.exceptionAsString());
print(details.stack);
}
// 发送错误报告
// ErrorReportingService.reportError(details);
};
// 设置未捕获异常处理
PlatformDispatcher.instance.onError = (Object error, StackTrace stack) {
if (kDebugMode) {
print(error);
print(stack);
}
// 发送错误报告
// ErrorReportingService.reportError(error, stack);
return true;
};
runApp(MyApp());
}
// services/error_reporting_service.dart
class ErrorReportingService {
static void reportError(Object error, [StackTrace? stack]) {
// 发送到错误监控服务(如Firebase Crashlytics)
// FirebaseCrashlytics.instance.recordError(error, stack);
}
}
测试最佳实践
TDD开发示例
dart
// 遵循TDD流程:先写测试再写实现
// 1. 首先写测试
// features/calculator/domain/usecases/add_numbers_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'add_numbers.dart';
void main() {
late AddNumbers usecase;
setUp(() {
usecase = AddNumbers();
});
test('should return sum of two positive numbers', () async {
// arrange
const tA = 5;
const tB = 3;
const tExpected = 8;
// act
final result = usecase(tA, tB);
// assert
expect(result, equals(tExpected));
});
test('should return correct sum when one number is negative', () async {
// arrange
const tA = 5;
const tB = -3;
const tExpected = 2;
// act
final result = usecase(tA, tB);
// assert
expect(result, equals(tExpected));
});
}
// 2. 然后实现功能
// features/calculator/domain/usecases/add_numbers.dart
class AddNumbers {
int call(int a, int b) {
return a + b;
}
}
Mock最佳实践
dart
// 使用Mockito进行依赖模拟
// features/user/data/repositories/user_repository_impl_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:fpdart/fpdart.dart';
import '../../../../../core/error/failure.dart';
import '../../../domain/entities/user.dart';
import '../../datasources/local/user_local_data_source.dart';
import '../../datasources/remote/user_remote_data_source.dart';
import '../models/user_model.dart';
import '../repositories/user_repository_impl.dart';
class MockRemoteDataSource extends Mock implements UserRemoteDataSource {}
class MockLocalDataSource extends Mock implements UserLocalDataSource {}
void main() {
late UserRepositoryImpl repository;
late MockRemoteDataSource mockRemoteDataSource;
late MockLocalDataSource mockLocalDataSource;
setUp(() {
mockRemoteDataSource = MockRemoteDataSource();
mockLocalDataSource = MockLocalDataSource();
repository = UserRepositoryImpl(
remoteDataSource: mockRemoteDataSource,
localDataSource: mockLocalDataSource,
);
});
group('getUser', () {
const tUserId = '123';
final tUserModel = UserModel(
id: tUserId,
name: 'Test User',
email: 'test@example.com',
);
final tUser = tUserModel.toEntity();
test('should return user when call to remote data source is successful', () async {
// arrange
when(mockRemoteDataSource.getUser(any))
.thenAnswer((_) async => tUserModel);
// act
final result = await repository.getUser(tUserId);
// assert
verify(mockRemoteDataSource.getUser(tUserId));
expect(result, equals(Right(tUser)));
});
test('should cache user locally when get user successfully', () async {
// arrange
when(mockRemoteDataSource.getUser(any))
.thenAnswer((_) async => tUserModel);
when(mockLocalDataSource.cacheUser(any))
.thenAnswer((_) async => Future.value());
// act
await repository.getUser(tUserId);
// assert
verify(mockLocalDataSource.cacheUser(tUserModel));
});
test('should return cached user when remote data source fails', () async {
// arrange
when(mockRemoteDataSource.getUser(any))
.thenThrow(ServerException('Server error'));
when(mockLocalDataSource.getCachedUser(any))
.thenAnswer((_) async => tUserModel);
// act
final result = await repository.getUser(tUserId);
// assert
expect(result, equals(Right(tUser)));
});
});
}
性能优化最佳实践
Widget优化
dart
// 使用const构造函数
class OptimizedWidget extends StatelessWidget {
const OptimizedWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// 使用const
title: const Text('Optimized App'),
// 使用const
backgroundColor: Colors.blue,
),
body: Column(
children: const [
// 使用const列表
Padding(
padding: EdgeInsets.all(16.0),
child: Text('This is an optimized widget tree'),
),
// 其他const子组件
],
),
);
}
}
// 使用Key优化列表
class OptimizedList extends StatelessWidget {
final List<Item> items;
const OptimizedList({Key? key, required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
// 使用ValueKey确保每个项目有唯一标识
return KeyedSubtree(
key: ValueKey(item.id),
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
),
);
},
);
}
}
// 使用RepaintBoundary隔离重绘
class IsolatedRepaintWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: CustomPaint(
painter: _CustomPainter(),
child: Container(
width: 200,
height: 200,
color: Colors.transparent,
),
),
);
}
}
class _CustomPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制逻辑
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
内存管理
dart
// 正确管理资源释放
class ResourceManagementWidget extends StatefulWidget {
@override
_ResourceManagementWidgetState createState() => _ResourceManagementWidgetState();
}
class _ResourceManagementWidgetState extends State<ResourceManagementWidget> {
Timer? _timer;
StreamSubscription? _subscription;
AnimationController? _animationController;
@override
void initState() {
super.initState();
// 初始化资源
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {});
});
_subscription = Stream.periodic(Duration(seconds: 2)).listen((event) {
print('Event: $event');
});
_animationController = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
}
@override
void dispose() {
// 正确释放所有资源
_timer?.cancel();
_subscription?.cancel();
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
代码质量最佳实践
代码审查清单
markdown
## 代码审查清单
### 功能性
- [ ] 代码实现了预期功能
- [ ] 边界条件得到妥善处理
- [ ] 错误情况得到适当处理
- [ ] 性能要求得到满足
### 代码质量
- [ ] 代码遵循项目编码规范
- [ ] 命名清晰、具有描述性
- [ ] 函数/方法职责单一
- [ ] 代码复杂度适中
- [ ] 重复代码被适当提取
### 测试
- [ ] 关键逻辑有相应测试
- [ ] 测试用例覆盖边界条件
- [ ] 测试命名清晰
- [ ] 测试独立且可重现
### 文档
- [ ] 公共API有适当注释
- [ ] 复杂逻辑有解释注释
- [ ] 变更内容在PR描述中说明
静态分析配置
yaml
# analysis_options.yaml
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "**/*.config.dart"
- "lib/generated/**"
strong-mode:
implicit-casts: false
implicit-dynamic: false
errors:
missing_return: error
dead_code: error
unused_element: error
unused_import: error
unused_local_variable: error
linter:
rules:
# 风格规则
- always_declare_return_types
- always_put_control_body_on_new_line
- always_put_required_named_parameters_first
- always_require_non_null_named_parameters
- annotate_overrides
- avoid_bool_literals_in_conditional_expressions
- avoid_catches_without_on_clauses
- avoid_catching_errors
- avoid_classes_with_only_static_members
- avoid_double_and_int_checks
- avoid_dynamic_calls
- avoid_empty_else
- avoid_equals_and_hash_code_on_mutable_classes
- avoid_escaping_inner_quotes
- avoid_field_initializers_in_const_classes
- avoid_final_parameters
- avoid_function_literals_in_foreach_calls
- avoid_implementing_value_types
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_multiple_declarations_per_line
- avoid_null_checks_in_equality_operators
- avoid_positional_boolean_parameters
- avoid_print
- avoid_private_typedef_functions
- avoid_redundant_argument_values
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null
- avoid_returning_null_for_void
- avoid_returning_this
- avoid_setters_without_getters
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_type_to_string
- avoid_types_as_parameter_names
- avoid_types_on_closure_parameters
- avoid_unnecessary_containers
- avoid_unused_constructor_parameters
- avoid_void_async
- avoid_web_libraries_in_flutter
- await_only_futures
- camel_case_extensions
- camel_case_types
- cancel_subscriptions
- cascade_invocations
- cast_nullable_to_non_nullable
- close_sinks
- constant_identifier_names
- control_flow_in_finally
- curly_braces_in_flow_control_structures
- depend_on_referenced_packages
- deprecated_consistency
- diagnostic_describe_all_properties
- directives_ordering
- do_not_use_environment
- empty_catches
- empty_constructor_bodies
- empty_statements
- eol_at_end_of_file
- exhaustive_cases
- file_names
- flutter_style_todos
- hash_and_equals
- implementation_imports
- invariant_booleans
- iterable_contains_unrelated_type
- join_return_with_assignment
- leading_newlines_in_multiline_strings
- library_names
- library_prefixes
- library_private_types_in_public_api
- lines_longer_than_80_chars
- list_remove_unrelated_type
- literal_only_boolean_expressions
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_default_cases
- no_duplicate_case_values
- no_logic_in_create_state
- no_runtimeType_toString
- non_constant_identifier_names
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
- omit_local_variable_types
- one_member_abstracts
- only_throw_errors
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- parameter_assignments
- prefer_asserts_in_initializer_lists
- prefer_asserts_with_message
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- prefer_equal_for_default_values
- prefer_expression_function_bodies
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
- prefer_foreach
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_int_literals
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
- prefer_mixin
- prefer_null_aware_method_calls
- prefer_null_aware_operators
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- provide_deprecation_message
- public_member_api_docs
- recursive_getters
- require_trailing_commas
- sized_box_for_whitespace
- slash_for_doc_comments
- sort_child_properties_last
- sort_constructors_first
- sort_pub_dependencies
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
- type_annotate_public_apis
- type_init_formals
- unawaited_futures
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_constructor_name
- unnecessary_final
- unnecessary_getters_setters
- unnecessary_lambdas
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_checks
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_raw_strings
- unnecessary_statements
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- unrelated_type_equality_checks
- unsafe_html
- use_build_context_synchronously
- use_full_hex_values_for_flutter_colors
- use_function_type_syntax_for_parameters
- use_if_null_to_convert_nulls_to_bools
- use_is_even_rather_than_modulo
- use_key_in_widget_constructors
- use_late_for_private_fields_and_variables
- use_named_constants
- use_raw_strings
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_string_buffers
- use_super_parameters
- use_test_throws_matchers
- use_to_and_as_if_applicable
- valid_regexps
- void_checks
依赖管理最佳实践
版本管理
yaml
# pubspec.yaml - 使用版本范围而非固定版本
dependencies:
flutter:
sdk: flutter
# 推荐使用向上兼容的版本范围
http: ^0.13.5
dio: ^5.0.0
flutter_bloc: ^8.1.3
cached_network_image: ^3.2.3
# 对于不稳定版本,使用更严格的版本控制
some_new_package: '>=1.0.0 <2.0.0'
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.4.0
build_runner: ^2.4.6
json_serializable: ^6.7.1
依赖注入
dart
// injection_container.dart
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:http/http.dart' as http;
import 'core/service_locator.config.dart';
final getIt = GetIt.instance;
@InjectableInit(
initializerName: r'$initGetIt',
preferRelativeImports: true,
asExtension: false,
)
void configureDependencies() => $initGetIt(getIt);
// 配置类
@module
abstract class RegisterModule {
@lazySingleton
http.Client get httpClient => http.Client();
@lazySingleton
NetworkInfo get networkInfo => NetworkInfoImpl();
}
安全最佳实践
数据安全
dart
// services/secure_storage_service.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService {
static final _storage = FlutterSecureStorage();
static Future<void> writeSecureData(String key, String value) async {
await _storage.write(key: key, value: value);
}
static Future<String?> readSecureData(String key) async {
return await _storage.read(key: key);
}
static Future<void> deleteSecureData(String key) async {
await _storage.delete(key: key);
}
static Future<void> clearAllSecureData() async {
await _storage.deleteAll();
}
}
API安全
dart
// services/api_service.dart
import 'package:dio/dio.dart';
class ApiService {
final Dio _dio;
ApiService(this._dio) {
// 配置安全相关的拦截器
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
// 添加认证头
final token = await SecureStorageService.readSecureData('auth_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
// 添加安全头
options.headers['X-Requested-With'] = 'XMLHttpRequest';
handler.next(options);
},
onResponse: (response, handler) {
// 检查响应安全
if (response.statusCode == 401) {
// 处理认证失败
SecureStorageService.deleteSecureData('auth_token');
}
handler.next(response);
},
onError: (DioException err, handler) {
// 统一错误处理
handler.next(err);
},
),
);
}
}
国际化最佳实践
dart
// l10n/app_localizations.dart (Generated)
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// 使用示例
class LocalizedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: Text(l10n.helloWorld),
),
);
}
}
总结
Flutter最佳实践涵盖了开发的各个方面:
- 项目结构:采用清晰的模块化结构
- 状态管理:选择合适的状态管理方案
- 代码组织:使用常量、扩展、工具类等方式组织代码
- 错误处理:实现统一的错误处理机制
- 测试:编写全面的测试用例
- 性能优化:关注UI渲染、内存管理等性能方面
- 代码质量:使用静态分析和代码审查确保质量
- 安全:保护用户数据和API安全
- 国际化:支持多语言
遵循这些最佳实践可以显著提高Flutter应用的质量、可维护性和用户体验。