Files
patbond-flutter/doc/main_app_const.md
T
2026-07-15 12:01:04 +08:00

9.2 KiB
Raw Blame History

main.dartapp.dartconst App() 说明

main.dartapp.dart 应该放什么

以常见 Flutter App 项目结构来看,main.dartapp.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
  • Scaffold
  • AppBar
  • 首页布局
  • 登录逻辑
  • 网络请求
  • 路由表
  • 大量主题配置
  • 业务状态管理

也就是说,不要让 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”的常见原因

原因 1App 构造函数不是 const

错误:

class App extends StatelessWidget {
  App({super.key});
}

正确:

class App extends StatelessWidget {
  const App({super.key});
}

原因 2App 里面继承的不是 Widget,或者写法不完整

正确:

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp();
  }
}

原因 3App 构造函数里有非 const 默认值或初始化逻辑

例如:

class App extends StatelessWidget {
  App({super.key}) {
    print('init');
  }
}

这种就不能是 const 构造函数。

原因 4App 构造函数接收了运行时对象

例如:

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 构造函数或参数里有运行时内容。