9.2 KiB
main.dart、app.dart 和 const App() 说明
main.dart 和 app.dart 应该放什么
以常见 Flutter App 项目结构来看,main.dart 和 app.dart 的职责应该分得很清楚:
main.dart:负责“启动前准备”和“启动 App”app.dart:负责“描述整个 App 长什么样、用什么主题、从哪个页面开始、怎么路由”
main.dart 应该放什么
main.dart 是程序入口,应该尽量薄。
最基础版本:
import 'package:flutter/material.dart';
import 'app/app.dart';
void main() {
runApp(const App());
}
如果项目刚开始,这样就够了。
稍微正式一点的 main.dart
如果后面需要初始化一些东西,可以这样:
import 'package:flutter/material.dart';
import 'app/app.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
WidgetsFlutterBinding.ensureInitialized() 的作用是:在 runApp 之前确保 Flutter 框架初始化完成。
什么时候需要它:
- 初始化
SharedPreferences - 初始化本地数据库
- 初始化 Firebase
- 加载
.env - 初始化本地存储
- 设置屏幕方向
- 初始化依赖注入
例如以后可能会变成:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await AppStorage.init();
await Firebase.initializeApp();
runApp(const App());
}
但现在不需要提前写这些。
main.dart 不应该放什么
不建议在 main.dart 里放:
- 页面 UI
ScaffoldAppBar- 首页布局
- 登录逻辑
- 网络请求
- 路由表
- 大量主题配置
- 业务状态管理
也就是说,不要让 main.dart 变成这样:
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(...),
body: ...,
),
));
}
这只适合临时 Demo,不适合项目长期维护。
app.dart 应该放什么
app.dart 放根组件,也就是整个应用壳子。
最基础版本:
import 'package:flutter/material.dart';
import '../features/home/home_page.dart';
import 'theme.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Patbond',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
home: const HomePage(),
);
}
}
这里负责:
- App 标题
- 是否显示右上角 debug 标识
- 全局主题
- 首页入口
- 路由配置
- 本地化配置
- 全局导航配置
如果暂时没有路由系统
项目刚开始,页面少,可以这样:
return MaterialApp(
title: 'Patbond',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
home: const HomePage(),
);
这种最简单。
适合:
- 只有首页
- 页面数量很少
- 暂时不需要登录拦截
- 暂时不需要深链接
- 暂时不需要命名路由
如果使用 Flutter 自带命名路由
可以这样:
return MaterialApp(
title: 'Patbond',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
initialRoute: '/',
routes: {
'/': (_) => const HomePage(),
'/login': (_) => const LoginPage(),
'/profile': (_) => const ProfilePage(),
},
);
适合小到中等项目。
优点:
- 简单
- 不需要额外依赖
- 比
Navigator.push字符串散落各处更统一
缺点:
- 登录拦截不够优雅
- 嵌套路由不方便
- Web URL 管理不如
go_router
如果以后使用 go_router
app.dart 会变成:
import 'package:flutter/material.dart';
import 'router.dart';
import 'theme.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Patbond',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
routerConfig: appRouter,
);
}
}
然后 router.dart 单独管理路由。
这种适合:
- 页面多
- Web 地址栏需要正常变化
- 有登录态跳转
- 有底部 Tab
- 有深链接
- 有权限拦截
app.dart 不应该放什么
不建议在 app.dart 里放:
- 某个页面的具体 UI
- 首页复杂布局
- 登录表单逻辑
- 网络请求实现
- 数据模型
- 业务判断细节
- 大量工具函数
app.dart 是应用壳,不是业务页面。
推荐当前项目的版本
当前项目还很新,建议先用最简单的版本。
目录:
lib/
main.dart
app/
app.dart
theme.dart
features/
home/
home_page.dart
main.dart:
import 'package:flutter/material.dart';
import 'app/app.dart';
void main() {
runApp(const App());
}
app/app.dart:
import 'package:flutter/material.dart';
import '../features/home/home_page.dart';
import 'theme.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Patbond',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
home: const HomePage(),
);
}
}
app/theme.dart:
import 'package:flutter/material.dart';
class AppTheme {
const AppTheme._();
static ThemeData get light {
return ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
),
useMaterial3: true,
);
}
}
features/home/home_page.dart:
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Patbond'),
),
);
}
}
职责总结
main.dart:
负责启动
应该包含:
runApp
启动前初始化
全局异常捕获入口
不应该包含:
页面 UI
业务逻辑
路由细节
主题细节
网络请求
app.dart:
负责 App 壳子
应该包含:
MaterialApp
主题配置入口
路由配置入口
首页入口
本地化配置入口
不应该包含:
具体页面布局
业务逻辑
接口请求
复杂状态处理
home_page.dart:
负责首页 UI
应该包含:
Scaffold
AppBar
body
首页交互
首页状态
关于 const App()
如果写:
import 'package:flutter/material.dart';
import 'app/app.dart';
void main() {
runApp(App());
}
IDE 提示要去掉或加上 const,通常是因为 App 构造函数是否支持 const。
在 Flutter 里,如果一个 Widget 可以在编译期确定,推荐写成:
runApp(const App());
而不是:
runApp(App());
正确写法
main.dart:
import 'package:flutter/material.dart';
import 'app/app.dart';
void main() {
runApp(const App());
}
app/app.dart 里面要这样写:
import 'package:flutter/material.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Patbond',
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: Text('Patbond'),
),
),
);
}
}
关键是这一行:
const App({super.key});
如果 App 类里没有 const 构造函数,比如是这样:
class App extends StatelessWidget {
App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(...);
}
}
那 runApp(const App()); 就不能用,IDE 可能会提示去掉 const。
为什么 Flutter 推荐 const
const 的好处是:
- 编译期创建对象
- 减少运行时重复创建 Widget
- Flutter rebuild 时更容易优化
- 代码语义更清楚:这个 Widget 本身没有可变状态
对于 StatelessWidget,只要构造参数也都是可 const 的,一般都应该写 const 构造函数。
看到“让我去掉 const”的常见原因
原因 1:App 构造函数不是 const
错误:
class App extends StatelessWidget {
App({super.key});
}
正确:
class App extends StatelessWidget {
const App({super.key});
}
原因 2:App 里面继承的不是 Widget,或者写法不完整
正确:
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp();
}
}
原因 3:App 构造函数里有非 const 默认值或初始化逻辑
例如:
class App extends StatelessWidget {
App({super.key}) {
print('init');
}
}
这种就不能是 const 构造函数。
原因 4:App 构造函数接收了运行时对象
例如:
class App extends StatelessWidget {
final DateTime now;
const App({super.key, required this.now});
}
调用时不能这样:
runApp(const App(now: DateTime.now()));
因为 DateTime.now() 不是编译期常量。
结论
优先写成:
void main() {
runApp(const App());
}
并确保 App 里面是:
class App extends StatelessWidget {
const App({super.key});
}
如果这样之后 IDE 还提示去掉 const,说明 App 构造函数或参数里有运行时内容。