Flutter 企业级项目完整架构指南
·
Flutter 完整的项目页面架构
我将为你展示一个企业级 Flutter 项目的完整页面架构设计,包含分层架构、状态管理、路由管理、组件化等最佳实践。
一、项目目录结构
lib/
├── main.dart # 应用入口
├── app/ # 应用层
│ ├── config/ # 应用配置
│ │ ├── app_config.dart # 应用配置类
│ │ ├── environment.dart # 环境配置
│ │ └── theme.dart # 主题配置
│ ├── di/ # 依赖注入
│ │ ├── injector.dart
│ │ └── service_locator.dart
│ └── bootstrap.dart # 应用启动器
├── core/ # 核心层
│ ├── constants/ # 常量
│ ├── errors/ # 错误处理
│ ├── extensions/ # 扩展方法
│ ├── utils/ # 工具类
│ ├── widgets/ # 核心组件
│ └── base/ # 基类
│ ├── base_page.dart
│ └── base_viewmodel.dart
├── data/ # 数据层
│ ├── models/ # 数据模型
│ ├── datasources/ # 数据源
│ │ ├── local/
│ │ └── remote/
│ ├── repositories/ # 仓库实现
│ └── mapper/ # 数据映射
├── domain/ # 领域层
│ ├── entities/ # 业务实体
│ ├── repositories/ # 仓库接口
│ └── usecases/ # 用例
├── presentation/ # 表现层
│ ├── pages/ # 页面
│ │ ├── auth/ # 认证模块
│ │ │ ├── login/
│ │ │ │ ├── login_page.dart
│ │ │ │ ├── login_viewmodel.dart
│ │ │ │ └── widgets/ # 页面专属组件
│ │ │ └── register/
│ │ ├── home/ # 首页模块
│ │ ├── profile/ # 个人中心
│ │ └── ...
│ ├── widgets/ # 共享组件
│ │ ├── common/
│ │ ├── forms/
│ │ └── dialogs/
│ └── state/ # 状态管理
│ ├── providers/
│ ├── blocs/
│ └── cubits/
└── routes/ # 路由管理
├── app_router.dart
├── route_names.dart
└── route_transitions.dart
二、分层架构详解
- 应用层 (app/)
// lib/app/config/app_config.dart
class AppConfig {
final String appName;
final String baseUrl;
final Environment environment;
final bool enableLogging;
AppConfig({
required this.appName,
required this.baseUrl,
required this.environment,
this.enableLogging = false,
});
static AppConfig dev() => AppConfig(
appName: 'Flutter App Dev',
baseUrl: 'https://api.dev.example.com',
environment: Environment.development,
enableLogging: true,
);
}
- 核心层 (core/)
// lib/core/base/base_page.dart
abstract class BasePage<T extends BaseViewModel> extends StatefulWidget {
const BasePage({Key? key}) : super(key: key);
_BasePageState<T> createState() => _BasePageState<T>();
}
class _BasePageState<T extends BaseViewModel> extends State<BasePage<T>> {
late T _viewModel;
void initState() {
super.initState();
_viewModel = getIt<T>();
_viewModel.initialize();
}
Widget build(BuildContext context) {
return Scaffold(
body: buildBody(context, _viewModel),
);
}
Widget buildBody(BuildContext context, T viewModel);
}
三、页面架构实现
- 页面基类设计
// lib/core/base/base_view.dart
abstract class BaseView<T extends BaseViewModel> extends StatelessWidget {
const BaseView({Key? key}) : super(key: key);
T get viewModel;
Widget buildBody(BuildContext context);
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>.value(
value: viewModel,
child: Consumer<T>(
builder: (context, vm, child) {
return Scaffold(
appBar: buildAppBar(context, vm),
body: buildBody(context),
bottomNavigationBar: buildBottomBar(context, vm),
floatingActionButton: buildFloatingActionButton(context, vm),
drawer: buildDrawer(context, vm),
);
},
),
);
}
// 可重写的组件方法
PreferredSizeWidget? buildAppBar(BuildContext context, T vm) => null;
Widget? buildBottomBar(BuildContext context, T vm) => null;
Widget? buildFloatingActionButton(BuildContext context, T vm) => null;
Widget? buildDrawer(BuildContext context, T vm) => null;
}
- ViewModel 基类
// lib/core/base/base_viewmodel.dart
abstract class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.idle;
String? _errorMessage;
ViewState get state => _state;
String? get errorMessage => _errorMessage;
bool get isLoading => _state == ViewState.loading;
bool get hasError => _state == ViewState.error;
void setState(ViewState newState) {
_state = newState;
notifyListeners();
}
void setError(String message) {
_errorMessage = message;
_state = ViewState.error;
notifyListeners();
}
Future<void> initialize() async {}
void dispose() {
super.dispose();
}
}
enum ViewState { idle, loading, success, error }
四、完整页面示例
- 登录页面实现
// lib/presentation/pages/auth/login/login_page.dart
class LoginPage extends BaseView<LoginViewModel> {
const LoginPage({Key? key}) : super(key: key);
LoginViewModel get viewModel => getIt<LoginViewModel>();
Widget buildBody(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: viewModel.formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildEmailField(),
const SizedBox(height: 20),
_buildPasswordField(),
const SizedBox(height: 30),
_buildLoginButton(context),
if (viewModel.hasError) _buildErrorText(),
],
),
),
);
}
Widget _buildEmailField() {
return TextFormField(
controller: viewModel.emailController,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
validator: viewModel.validateEmail,
keyboardType: TextInputType.emailAddress,
);
}
Widget _buildPasswordField() {
return TextFormField(
controller: viewModel.passwordController,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(),
),
obscureText: true,
validator: viewModel.validatePassword,
);
}
Widget _buildLoginButton(BuildContext context) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: viewModel.isLoading ? null : () => viewModel.login(context),
child: viewModel.isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
);
}
Widget _buildErrorText() {
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(
viewModel.errorMessage!,
style: const TextStyle(color: Colors.red),
),
);
}
AppBar? buildAppBar(BuildContext context, LoginViewModel vm) {
return AppBar(
title: const Text('Login'),
centerTitle: true,
);
}
}
- Login ViewModel
// lib/presentation/pages/auth/login/login_viewmodel.dart
class LoginViewModel extends BaseViewModel {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final LoginUseCase _loginUseCase;
final SaveUserUseCase _saveUserUseCase;
LoginViewModel(this._loginUseCase, this._saveUserUseCase);
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Enter a valid email';
}
return null;
}
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
}
Future<void> login(BuildContext context) async {
if (!formKey.currentState!.validate()) return;
setState(ViewState.loading);
try {
final result = await _loginUseCase.execute(LoginParams(
email: emailController.text,
password: passwordController.text,
));
await _saveUserUseCase.execute(result.user);
setState(ViewState.success);
// 导航到主页
Navigator.of(context).pushNamedAndRemoveUntil(
RouteNames.home,
(route) => false,
);
} on ApiException catch (e) {
setError(e.message);
} catch (e) {
setError('An unexpected error occurred');
}
}
void dispose() {
emailController.dispose();
passwordController.dispose();
super.dispose();
}
}
五、路由管理
- 路由配置
// lib/routes/app_router.dart
class AppRouter {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RouteNames.splash:
return MaterialPageRoute(builder: (_) => const SplashPage());
case RouteNames.login:
return MaterialPageRoute(builder: (_) => const LoginPage());
case RouteNames.register:
return MaterialPageRoute(builder: (_) => const RegisterPage());
case RouteNames.home:
return MaterialPageRoute(builder: (_) => const HomePage());
case RouteNames.profile:
return MaterialPageRoute(builder: (_) => const ProfilePage());
case RouteNames.settings:
return MaterialPageRoute(builder: (_) => const SettingsPage());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
),
);
}
}
static Map<String, WidgetBuilder> routes = {
RouteNames.splash: (context) => const SplashPage(),
RouteNames.login: (context) => const LoginPage(),
RouteNames.register: (context) => const RegisterPage(),
RouteNames.home: (context) => const HomePage(),
RouteNames.profile: (context) => const ProfilePage(),
};
}
- 路由名称常量
// lib/routes/route_names.dart
class RouteNames {
static const String splash = '/';
static const String login = '/login';
static const String register = '/register';
static const String home = '/home';
static const String profile = '/profile';
static const String settings = '/settings';
static const String productDetail = '/product/:id';
static String productDetailPath(String id) => '/product/$id';
}
六、状态管理(Provider + Riverpod)
- 全局状态提供器
// lib/presentation/state/providers/app_provider.dart
final appProvider = ChangeNotifierProvider<AppViewModel>((ref) {
return AppViewModel();
});
class AppViewModel extends ChangeNotifier {
ThemeMode _themeMode = ThemeMode.light;
Locale _locale = const Locale('en');
User? _currentUser;
ThemeMode get themeMode => _themeMode;
Locale get locale => _locale;
User? get currentUser => _currentUser;
bool get isLoggedIn => _currentUser != null;
void toggleTheme() {
_themeMode = _themeMode == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
notifyListeners();
}
void setLocale(Locale locale) {
_locale = locale;
notifyListeners();
}
void setUser(User? user) {
_currentUser = user;
notifyListeners();
}
}
- 使用 Riverpod 2.0
// lib/presentation/state/providers/auth_provider.dart
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref.watch(authRepositoryProvider));
});
class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository _repository;
AuthNotifier(this._repository) : super(const AuthState.initial());
Future<void> login(String email, String password) async {
state = const AuthState.loading();
try {
final user = await _repository.login(email, password);
state = AuthState.authenticated(user);
} on ApiException catch (e) {
state = AuthState.error(e.message);
}
}
Future<void> logout() async {
await _repository.logout();
state = const AuthState.unauthenticated();
}
}
class AuthState with _$AuthState {
const factory AuthState.initial() = _Initial;
const factory AuthState.loading() = _Loading;
const factory AuthState.authenticated(User user) = _Authenticated;
const factory AuthState.unauthenticated() = _Unauthenticated;
const factory AuthState.error(String message) = _Error;
}
七、组件化架构
- 可复用组件库
// lib/presentation/widgets/common/app_button.dart
class AppButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isLoading;
final bool isEnabled;
final ButtonType type;
const AppButton({
Key? key,
required this.text,
required this.onPressed,
this.isLoading = false,
this.isEnabled = true,
this.type = ButtonType.primary,
}) : super(key: key);
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: isEnabled && !isLoading ? onPressed : null,
style: _getButtonStyle(context),
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(
text,
style: Theme.of(context).textTheme.button,
),
);
}
ButtonStyle _getButtonStyle(BuildContext context) {
switch (type) {
case ButtonType.primary:
return ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
);
case ButtonType.secondary:
return ElevatedButton.styleFrom(
backgroundColor: Colors.grey[300],
);
case ButtonType.danger:
return ElevatedButton.styleFrom(
backgroundColor: Colors.red,
);
}
}
}
enum ButtonType { primary, secondary, danger }
- 表单组件
// lib/presentation/widgets/forms/app_text_field.dart
class AppTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
final String? hint;
final IconData? prefixIcon;
final IconData? suffixIcon;
final bool obscureText;
final TextInputType? keyboardType;
final String? Function(String?)? validator;
final VoidCallback? onSuffixPressed;
const AppTextField({
Key? key,
required this.controller,
required this.label,
this.hint,
this.prefixIcon,
this.suffixIcon,
this.obscureText = false,
this.keyboardType,
this.validator,
this.onSuffixPressed,
}) : super(key: key);
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: label,
hintText: hint,
prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null,
suffixIcon: suffixIcon != null
? IconButton(
icon: Icon(suffixIcon),
onPressed: onSuffixPressed,
)
: null,
border: const OutlineInputBorder(),
filled: true,
fillColor: Colors.grey[100],
),
obscureText: obscureText,
keyboardType: keyboardType,
validator: validator,
);
}
}
八、依赖注入配置
// lib/app/di/injector.dart
final getIt = GetIt.instance;
Future<void> init() async {
// 第三方服务
await _initExternalServices();
// 数据层
_initDataLayer();
// 领域层
_initDomainLayer();
// 表现层
_initPresentationLayer();
}
void _initPresentationLayer() {
// ViewModels
getIt.registerFactory(() => LoginViewModel(
getIt<LoginUseCase>(),
getIt<SaveUserUseCase>(),
));
getIt.registerFactory(() => HomeViewModel(
getIt<GetProductsUseCase>(),
));
}
void _initDomainLayer() {
// UseCases
getIt.registerLazySingleton(() => LoginUseCase(getIt()));
getIt.registerLazySingleton(() => GetProductsUseCase(getIt()));
getIt.registerLazySingleton(() => SaveUserUseCase(getIt()));
}
void _initDataLayer() {
// 仓库
getIt.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(
localDataSource: getIt(),
remoteDataSource: getIt(),
),
);
// 数据源
getIt.registerLazySingleton<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImpl(getIt()),
);
getIt.registerLazySingleton<AuthLocalDataSource>(
() => AuthLocalDataSourceImpl(getIt()),
);
}
九、应用入口配置
// lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化依赖注入
await init();
// 运行应用
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => getIt<AppViewModel>()),
Provider(create: (_) => getIt<AuthRepository>()),
],
child: Consumer<AppViewModel>(
builder: (context, appViewModel, child) {
return MaterialApp(
title: 'Flutter Enterprise App',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: appViewModel.themeMode,
locale: appViewModel.locale,
debugShowCheckedModeBanner: false,
initialRoute: RouteNames.splash,
onGenerateRoute: AppRouter.generateRoute,
navigatorKey: NavigationService.navigatorKey,
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: 1.0, // 防止系统字体缩放
),
child: child!,
);
},
);
},
),
);
}
}
十、最佳实践总结
- 严格分层:保持清晰的关注点分离
- 单一职责:每个类/文件只做一件事
- 依赖倒置:依赖抽象,不依赖具体实现
- 响应式设计:使用状态管理确保UI与数据同步
- 错误处理:统一的错误处理机制
- 国际化支持:为多语言做好准备
- 主题管理:支持明暗主题切换
- 性能优化:懒加载、分页、缓存策略
- 测试友好:依赖注入便于单元测试
- 可扩展性:模块化设计,便于功能扩展
这个架构支持大型企业级应用的开发,具有良好的可维护性、可测试性和可扩展性。可以根据项目需求进行调整和扩展。
更多推荐

所有评论(0)