当前位置: 首页 > news >正文

Flutter项目搭建最佳实践

1. 项目结构规范

lib/
├── core/                    # 核心层
│   ├── base/               # 基类封装
│   ├── constants/          # 常量定义
│   ├── theme/              # 主题配置
│   ├── localization/       # 国际化
│   └── utils/              # 工具类
├── data/                   # 数据层
│   ├── models/             # 数据模型
│   ├── repositories/       # 数据仓库
│   ├── datasources/        # 数据源
│   └── network/            # 网络请求
├── domain/                 # 业务层
│   ├── entities/           # 业务实体
│   ├── repositories/       # 抽象仓库
│   └── usecases/           # 用例
├── presentation/           # 表现层
│   ├── blocs/              # BLoC状态管理
│   ├── pages/              # 页面
│   ├── widgets/            # 公共组件
│   ├── dialogs/            # 对话框
│   └── animations/         # 动画
└── main.dart

2. Base基类封装

基础Widget封装

// core/base/base_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';abstract class BaseStatefulWidget extends StatefulWidget {const BaseStatefulWidget({Key? key}) : super(key: key);
}abstract class BaseState<T extends BaseStatefulWidget> extends State<T> {bool _isLoading = false;void initState() {super.initState();initBase();}void initBase() {// 基础初始化逻辑}void showLoading() {if (!_isLoading) {_isLoading = true;// 显示加载对话框}}void hideLoading() {if (_isLoading) {_isLoading = false;// 隐藏加载对话框}}void showError(String message) {// 错误提示}void showSuccess(String message) {// 成功提示}
}

BLoC基类封装

// core/base/base_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';abstract class BaseBloc<Event, State> extends Bloc<Event, State> {BaseBloc(State initialState) : super(initialState);void onEvent(Event event) {super.onEvent(event);// 统一事件处理}void onChange(Change<State> change) {super.onChange(change);// 统一状态变更处理}void onError(Object error, StackTrace stackTrace) {// 统一错误处理super.onError(error, stackTrace);}
}

3. 日志类封装

// core/utils/logger.dart
import 'package:logger/logger.dart';class AppLogger {static final Logger _logger = Logger(printer: PrettyPrinter(methodCount: 0,errorMethodCount: 8,lineLength: 120,colors: true,printEmojis: true,printTime: true,),);static void v(dynamic message, [dynamic error, StackTrace? stackTrace]) {_logger.v(message, error, stackTrace);}static void d(dynamic message, [dynamic error, StackTrace? stackTrace]) {_logger.d(message, error, stackTrace);}static void i(dynamic message, [dynamic error, StackTrace? stackTrace]) {_logger.i(message, error, stackTrace);}static void w(dynamic message, [dynamic error, StackTrace? stackTrace]) {_logger.w(message, error, stackTrace);}static void e(dynamic message, [dynamic error, StackTrace? stackTrace]) {_logger.e(message, error, stackTrace);}static void wtf(dynamic message, [dynamic error, StackTrace? stackTrace]) {_logger.wtf(message, error, stackTrace);}
}

4. 主题封装

// core/theme/app_theme.dart
import 'package:flutter/material.dart';class AppTheme {static const Color primaryColor = Color(0xFF6200EE);static const Color secondaryColor = Color(0xFF03DAC6);static const Color errorColor = Color(0xFFB00020);static const Color backgroundColor = Color(0xFFF5F5F5);static ThemeData get lightTheme {return ThemeData(brightness: Brightness.light,primaryColor: primaryColor,colorScheme: const ColorScheme.light().copyWith(primary: primaryColor,secondary: secondaryColor,error: errorColor,),scaffoldBackgroundColor: backgroundColor,appBarTheme: const AppBarTheme(backgroundColor: primaryColor,foregroundColor: Colors.white,elevation: 0,),textTheme: const TextTheme(headlineLarge: TextStyle(fontSize: 32,fontWeight: FontWeight.bold,color: Colors.black,),bodyLarge: TextStyle(fontSize: 16,color: Colors.black87,),),);}static ThemeData get darkTheme {return ThemeData(brightness: Brightness.dark,primaryColor: primaryColor,colorScheme: const ColorScheme.dark().copyWith(primary: primaryColor,secondary: secondaryColor,error: errorColor,),);}
}

5. 国际化语言处理

// core/localization/app_localizations.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';class AppLocalizations {final Locale locale;AppLocalizations(this.locale);static AppLocalizations? of(BuildContext context) {return Localizations.of<AppLocalizations>(context, AppLocalizations);}static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();static final Map<String, Map<String, String>> _localizedValues = {'en': {'title': 'App Title','welcome': 'Welcome','error': 'An error occurred',},'zh': {'title': '应用标题','welcome': '欢迎','error': '发生了一个错误',},};String? get title {return _localizedValues[locale.languageCode]?['title'];}String? get welcome {return _localizedValues[locale.languageCode]?['welcome'];}String? get error {return _localizedValues[locale.languageCode]?['error'];}
}class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {const _AppLocalizationsDelegate();bool isSupported(Locale locale) {return ['en', 'zh'].contains(locale.languageCode);}Future<AppLocalizations> load(Locale locale) async {return AppLocalizations(locale);}bool shouldReload(_AppLocalizationsDelegate old) => false;
}

6. 字符串工具类封装

// core/utils/string_utils.dart
class StringUtils {static bool isEmpty(String? str) {return str == null || str.isEmpty;}static bool isNotEmpty(String? str) {return !isEmpty(str);}static String safeString(String? str, {String defaultValue = ''}) {return str ?? defaultValue;}static String formatCurrency(double amount, {String symbol = '¥'}) {return '$symbol${amount.toStringAsFixed(2)}';}static String truncateWithEllipsis(String str, int maxLength) {if (str.length <= maxLength) return str;return '${str.substring(0, maxLength)}...';}static bool isEmail(String email) {final RegExp emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');return emailRegex.hasMatch(email);}static bool isPhone(String phone) {final RegExp phoneRegex = RegExp(r'^1[3-9]\d{9}$');return phoneRegex.hasMatch(phone);}
}

7. 动画工具类封装

// core/utils/animation_utils.dart
import 'package:flutter/material.dart';class AnimationUtils {static const Duration fastDuration = Duration(milliseconds: 200);static const Duration mediumDuration = Duration(milliseconds: 300);static const Duration slowDuration = Duration(milliseconds: 500);static Curve get easeInOut => Curves.easeInOut;static Curve get bounceOut => Curves.bounceOut;static Curve get elasticOut => Curves.elasticOut;static Widget fadeIn(Widget child, Animation<double> animation) {return FadeTransition(opacity: animation,child: child,);}static Widget slideInFromBottom(Widget child, Animation<double> animation) {return SlideTransition(position: Tween<Offset>(begin: const Offset(0, 1),end: Offset.zero,).animate(animation),child: child,);}static Widget scaleIn(Widget child, Animation<double> animation) {return ScaleTransition(scale: animation,child: child,);}static Future<void> delayed(VoidCallback callback,{Duration duration = const Duration(milliseconds: 300)}) {return Future.delayed(duration, callback);}
}

8. 路由封装

// core/utils/route_utils.dart
import 'package:flutter/material.dart';class RouteUtils {static Future<T?> push<T>(BuildContext context, Widget page) {return Navigator.push<T>(context,MaterialPageRoute(builder: (context) => page),);}static Future<T?> pushReplacement<T>(BuildContext context, Widget page) {return Navigator.pushReplacement<T, T>(context,MaterialPageRoute(builder: (context) => page),);}static void pop<T>(BuildContext context, [T? result]) {Navigator.pop(context, result);}static Future<T?> pushNamed<T>(BuildContext context, String routeName,{Object? arguments}) {return Navigator.pushNamed<T>(context,routeName,arguments: arguments,);}static void popUntil(BuildContext context, String routeName) {Navigator.popUntil(context, ModalRoute.withName(routeName));}static bool canPop(BuildContext context) {return Navigator.canPop(context);}
}class AppRoutes {static const String home = '/home';static const String login = '/login';static const String profile = '/profile';static const String settings = '/settings';static Route<dynamic>? generateRoute(RouteSettings settings) {switch (settings.name) {case home:// return MaterialPageRoute(builder: (_) => HomePage());case login:// return MaterialPageRoute(builder: (_) => LoginPage());default:return null;}}
}

9. BLoC状态管理封装

BLoC基类增强

// presentation/blocs/base_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';abstract class BaseBloc<Event, State> extends Bloc<Event, State> {BaseBloc(State initialState) : super(initialState);void onEvent(Event event) {AppLogger.d('BLoC Event: $event');super.onEvent(event);}void onChange(Change<State> change) {AppLogger.d('BLoC State Change: ${change.currentState} -> ${change.nextState}');super.onChange(change);}
}

示例:计数器BLoC

// presentation/blocs/counter/counter_bloc.dart
part of 'counter_event.dart';
part of 'counter_state.dart';class CounterBloc extends BaseBloc<CounterEvent, CounterState> {CounterBloc() : super(const CounterState()) {on<CounterIncremented>(_onIncremented);on<CounterDecremented>(_onDecremented);on<CounterReset>(_onReset);}void _onIncremented(CounterIncremented event,Emitter<CounterState> emit,) {emit(state.copyWith(count: state.count + 1));}void _onDecremented(CounterDecremented event,Emitter<CounterState> emit,) {emit(state.copyWith(count: state.count - 1));}void _onReset(CounterReset event,Emitter<CounterState> emit,) {emit(state.copyWith(count: 0));}
}// presentation/blocs/counter/counter_event.dart
part of 'counter_bloc.dart';abstract class CounterEvent extends Equatable {const CounterEvent();List<Object> get props => [];
}class CounterIncremented extends CounterEvent {const CounterIncremented();
}class CounterDecremented extends CounterEvent {const CounterDecremented();
}class CounterReset extends CounterEvent {const CounterReset();
}// presentation/blocs/counter/counter_state.dart
part of 'counter_bloc.dart';class CounterState extends Equatable {final int count;const CounterState({this.count = 0});CounterState copyWith({int? count,}) {return CounterState(count: count ?? this.count,);}List<Object> get props => [count];
}

10. Retrofit风格网络请求封装

Dio配置

// data/network/dio_client.dart
import 'package:dio/dio.dart';
import 'package:logger/logger.dart';class DioClient {static final DioClient _instance = DioClient._internal();factory DioClient() => _instance;late Dio _dio;DioClient._internal() {_dio = Dio(BaseOptions(baseUrl: 'https://api.example.com',connectTimeout: const Duration(seconds: 30),receiveTimeout: const Duration(seconds: 30),sendTimeout: const Duration(seconds: 30),headers: {'Content-Type': 'application/json',},),);// 添加拦截器_dio.interceptors.add(LogInterceptor(request: true,requestBody: true,responseBody: true,logPrint: (object) => Logger().d(object),));_dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {// 添加认证token// options.headers['Authorization'] = 'Bearer $token';return handler.next(options);},onError: (DioException error, handler) {// 统一错误处理return handler.next(error);},));}Dio get dio => _dio;
}

API服务封装

// data/network/api_service.dart
import 'package:dio/dio.dart';
import 'dio_client.dart';class ApiService {final Dio _dio = DioClient().dio;Future<Response<T>> get<T>(String path, {Map<String, dynamic>? queryParameters,Options? options,CancelToken? cancelToken,ProgressCallback? onReceiveProgress,}) async {try {final response = await _dio.get<T>(path,queryParameters: queryParameters,options: options,cancelToken: cancelToken,onReceiveProgress: onReceiveProgress,);return response;} on DioException catch (e) {throw _handleError(e);}}Future<Response<T>> post<T>(String path, {dynamic data,Map<String, dynamic>? queryParameters,Options? options,CancelToken? cancelToken,ProgressCallback? onSendProgress,ProgressCallback? onReceiveProgress,}) async {try {final response = await _dio.post<T>(path,data: data,queryParameters: queryParameters,options: options,cancelToken: cancelToken,onSendProgress: onSendProgress,onReceiveProgress: onReceiveProgress,);return response;} on DioException catch (e) {throw _handleError(e);}}dynamic _handleError(DioException error) {switch (error.type) {case DioExceptionType.connectionTimeout:throw '连接超时';case DioExceptionType.sendTimeout:throw '发送超时';case DioExceptionType.receiveTimeout:throw '接收超时';case DioExceptionType.badCertificate:throw '证书错误';case DioExceptionType.badResponse:throw '服务器错误: ${error.response?.statusCode}';case DioExceptionType.cancel:throw '请求取消';case DioExceptionType.connectionError:throw '连接错误';case DioExceptionType.unknown:throw '网络错误';}}
}

Retrofit风格API接口

// data/network/apis/user_api.dart
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';part 'user_api.g.dart';(baseUrl: "https://api.example.com/")
abstract class UserApi {factory UserApi(Dio dio, {String baseUrl}) = _UserApi;("/users/{id}")Future<HttpResponse<User>> getUser(("id") String id);("/users")Future<HttpResponse<User>> createUser(() User user);("/users/{id}")Future<HttpResponse<User>> updateUser(("id") String id, () User user);("/users/{id}")Future<HttpResponse<void>> deleteUser(("id") String id);
}

11. Dialog封装

// presentation/dialogs/app_dialog.dart
import 'package:flutter/material.dart';class AppDialog {static Future<void> showLoading(BuildContext context, {String? message}) {return showDialog(context: context,barrierDismissible: false,builder: (context) => AlertDialog(content: Row(children: [const CircularProgressIndicator(),const SizedBox(width: 16),Text(message ?? '加载中...'),],),),);}static void hideLoading(BuildContext context) {Navigator.of(context, rootNavigator: true).pop();}static Future<bool?> showConfirmDialog(BuildContext context, {required String title,required String content,String confirmText = '确认',String cancelText = '取消',}) {return showDialog<bool>(context: context,builder: (context) => AlertDialog(title: Text(title),content: Text(content),actions: [TextButton(onPressed: () => Navigator.of(context).pop(false),child: Text(cancelText),),TextButton(onPressed: () => Navigator.of(context).pop(true),child: Text(confirmText),),],),);}static Future<void> showErrorDialog(BuildContext context, {required String message,String title = '错误',}) {return showDialog(context: context,builder: (context) => AlertDialog(title: Text(title),content: Text(message),actions: [TextButton(onPressed: () => Navigator.of(context).pop(),child: const Text('确定'),),],),);}static Future<String?> showInputDialog(BuildContext context, {required String title,String hintText = '请输入',String confirmText = '确定',String cancelText = '取消',}) {final TextEditingController controller = TextEditingController();return showDialog<String>(context: context,builder: (context) => AlertDialog(title: Text(title),content: TextField(controller: controller,decoration: InputDecoration(hintText: hintText),),actions: [TextButton(onPressed: () => Navigator.of(context).pop(),child: Text(cancelText),),TextButton(onPressed: () => Navigator.of(context).pop(controller.text),child: Text(confirmText),),],),);}
}

12. 底部弹窗封装

// presentation/dialogs/bottom_sheets.dart
import 'package:flutter/material.dart';class AppBottomSheets {static Future<T?> showCustomBottomSheet<T>({required BuildContext context,required Widget child,bool isScrollControlled = false,Color? backgroundColor,double? elevation,ShapeBorder? shape,Clip? clipBehavior,BoxConstraints? constraints,Color? barrierColor,bool enableDrag = true,bool isDismissible = true,bool useRootNavigator = false,RouteSettings? routeSettings,}) {return showModalBottomSheet<T>(context: context,isScrollControlled: isScrollControlled,backgroundColor: backgroundColor,elevation: elevation,shape: shape ?? const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16)),),clipBehavior: clipBehavior,constraints: constraints,barrierColor: barrierColor,enableDrag: enableDrag,isDismissible: isDismissible,useRootNavigator: useRootNavigator,routeSettings: routeSettings,builder: (context) => child,);}static Future<int?> showActionSheet({required BuildContext context,required List<String> options,String? title,String? cancelText = '取消',}) {return showCustomBottomSheet<int>(context: context,isScrollControlled: true,child: Container(padding: const EdgeInsets.all(16),child: Column(mainAxisSize: MainAxisSize.min,children: [if (title != null) ...[Text(title,style: Theme.of(context).textTheme.titleMedium,),const SizedBox(height: 16),],...List.generate(options.length, (index) {return Column(children: [ListTile(title: Text(options[index]),onTap: () => Navigator.of(context).pop(index),),if (index < options.length - 1)const Divider(height: 1),],);}),const SizedBox(height: 8),Container(width: double.infinity,child: ElevatedButton(onPressed: () => Navigator.of(context).pop(),style: ElevatedButton.styleFrom(backgroundColor: Theme.of(context).cardColor,foregroundColor: Theme.of(context).textTheme.bodyLarge?.color,),child: Text(cancelText!),),),],),),);}
}

13. 常用工具类封装

// core/utils/common_utils.dart
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:package_info_plus/package_info_plus.dart';class CommonUtils {static Future<bool> checkNetworkConnection() async {final connectivityResult = await Connectivity().checkConnectivity();return connectivityResult != ConnectivityResult.none;}static Future<String> getAppVersion() async {final PackageInfo packageInfo = await PackageInfo.fromPlatform();return '${packageInfo.version} (${packageInfo.buildNumber})';}static String formatFileSize(int bytes) {if (bytes <= 0) return "0 B";const suffixes = ["B", "KB", "MB", "GB", "TB"];final i = (log(bytes) / log(1024)).floor();return '${(bytes / pow(1024, i)).toStringAsFixed(2)} ${suffixes[i]}';}static String getTimeAgo(DateTime date) {final now = DateTime.now();final difference = now.difference(date);if (difference.inDays > 365) {return '${(difference.inDays / 365).floor()}年前';} else if (difference.inDays > 30) {return '${(difference.inDays / 30).floor()}月前';} else 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 '刚刚';}}
}// core/utils/device_utils.dart
import 'package:device_info_plus/device_info_plus.dart';class DeviceUtils {static Future<String> getDeviceInfo() async {final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();if (Platform.isAndroid) {final androidInfo = await deviceInfo.androidInfo;return '${androidInfo.manufacturer} ${androidInfo.model}';} else if (Platform.isIOS) {final iosInfo = await deviceInfo.iosInfo;return '${iosInfo.name} ${iosInfo.model}';}return 'Unknown Device';}
}

14. 打包配置

pubspec.yaml配置

name: flutter_best_practice
description: A Flutter Best Practice Projectpublish_to: 'none'version: 1.0.0+1environment:sdk: '>=3.0.0 <4.0.0'dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.2bloc: ^8.1.2flutter_bloc: ^8.1.3equatable: ^2.0.5dio: ^5.3.2retrofit: ^4.0.1logger: ^1.1.0connectivity_plus: ^5.0.1package_info_plus: ^4.2.0device_info_plus: ^9.0.3intl: ^0.18.1dev_dependencies:flutter_test:sdk: flutterflutter_lints: ^2.0.0build_runner: ^2.4.4retrofit_generator: ^4.2.0flutter:uses-material-design: truegenerate: trueassets:- assets/images/- assets/icons/- assets/translations/

环境配置

// core/constants/app_constants.dart
class AppConstants {static const String appName = 'Flutter Best Practice';static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL',defaultValue: 'https://api.example.com',);static const bool isDebug = bool.fromEnvironment('DEBUG', defaultValue: false);
}// flutter run --dart-define=API_BASE_URL=https://dev.api.example.com --dart-define=DEBUG=true
// flutter build apk --dart-define=API_BASE_URL=https://prod.api.example.com --dart-define=DEBUG=false

15. 其他重要补充

依赖注入配置

// core/di/injector.dart
import 'package:get_it/get_it.dart';final GetIt injector = GetIt.instance;class DependencyInjection {static Future<void> init() async {// 网络相关injector.registerLazySingleton(() => DioClient());injector.registerLazySingleton(() => ApiService());// 数据仓库// injector.registerLazySingleton<UserRepository>(() => UserRepositoryImpl());// BLoC// injector.registerFactory(() => CounterBloc());}
}

错误处理全局配置

// core/error/error_handler.dart
import 'package:flutter/foundation.dart';class ErrorHandler {static void setup() {FlutterError.onError = (FlutterErrorDetails details) {if (kDebugMode) {FlutterError.dumpErrorToConsole(details);} else {// 生产环境错误上报_reportError(details.exception, details.stack);}};PlatformDispatcher.instance.onError = (error, stack) {_reportError(error, stack);return true;};}static void _reportError(Object error, StackTrace? stack) {// 错误上报到服务器AppLogger.e('Global Error: $error', error, stack);}
}

性能监控

// core/utils/performance_utils.dart
import 'package:flutter/foundation.dart';class PerformanceUtils {static Future<T> measurePerformance<T>(String taskName,Future<T> Function() task,) async {final Stopwatch stopwatch = Stopwatch()..start();final T result = await task();stopwatch.stop();AppLogger.i('$taskName took ${stopwatch.elapsedMilliseconds}ms');return result;}static T measureSyncPerformance<T>(String taskName,T Function() task,) {final Stopwatch stopwatch = Stopwatch()..start();final T result = task();stopwatch.stop();AppLogger.i('$taskName took ${stopwatch.elapsedMilliseconds}ms');return result;}
}

这个最佳实践涵盖了Flutter项目开发的核心方面,提供了完整的项目结构和代码示例。可以根据自身项目需求和自己的理解进一步封装或者重构

http://www.dtcms.com/a/524108.html

相关文章:

  • # AI高精度提示词生成项目——3D-VR 课件—— 最终仓库级 AI 提示词:生成《EduVR Studio》—— 专业级 3D-VR 课件创作平台
  • 巡检机器人落地攻略:RK3576驱动12路低延迟视觉
  • 网站开发 文件上传慢wordpress 上线到centos
  • 嘉兴网站建设多少钱广州装修公司口碑最好的是哪家
  • Docker Swarm 的负载均衡和平滑切换原理
  • RabbitMQ 发送方确认机制详解
  • Keepalived 多节点负载均衡配置
  • Windows下载安装配置rabbitmq
  • 了解前端连接 RabbitMQ 的方式
  • 【ROS2】ROS2+Qt6在编译时报错:target_link_libraries
  • 从0到1理解智能体模式
  • 怎么做家具定制网站qq自动发货平台网站怎么做
  • 微网站开发合同网站建设项目付款方式
  • HarmonyOS ArkUI框架中AceContainer类的成员变量定义
  • 数据结构——希尔排序
  • 分组卷积(Grouped Convolution)原理与应用详解
  • 【信道利用率】为什么卫星链路用 SW 协议效率低?ARQ 信道利用率公式 + 计算题全解!
  • 三极管MOS管
  • PHP拆分重组pdf,php拆分pdf指定页数,并合并成新pdf
  • 详解C语言数组
  • 鹤山做网站公司建设网站宣传
  • 微信网站开发视频教程开发一个小软件多少钱
  • 释放内存与加速推理:PyTorch的torch.no_grad()与torch.inference_mode()
  • 论文笔记(九十六)VGGT: Visual Geometry Grounded Transformer
  • 城市基础设施安全运行监管平台
  • 网络 UDP 和 TCP / IP详细介绍
  • 数据结构(8)
  • [cpprestsdk] ~异步流处理(eg`basic_istream`、`basic_ostream`、`streambuf`) 底层
  • Linux 查找符合条件的文档
  • ​九小场所 / 乡镇监督防火 ——1 个平台管水源 / 隐患,整改率提 80%