Flutter 应用架构从入门到可扩展的演进实践
一个干净、模块化、支持 Riverpod 状态管理的起点
很多 Flutter 项目后面难维护,问题通常不在某个具体组件,而是最开始没把边界划清楚。代码能跑不代表架构站得住,尤其是当主题、持久化、跨页面状态慢慢都要加进来时,早期随手堆出来的结构会很快露出问题。下面这版从 v1.0 的最小骨架开始,走到 v2.0 的 Riverpod 架构,保留代码主线,也把中间的取舍说清楚。
v1.0:先把骨架搭正
这一阶段只做几件事:能启动、目录清楚、没有多余依赖。目标不是'功能丰富',而是别让项目一开始就长歪。
项目结构
lib/
├── main.dart
├── app.dart
├── screens/home_screen.dart
└── widgets/app_bar_title.dart
lib/main.dart
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(const MyApp());
}
main() 只负责启动。业务逻辑放这里,后面基本都会后悔,所以这里保持干净就够了。
lib/app.dart
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
static bool get isDarkMode => false;
// 预留:未来可动态控制
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
brightness: Brightness.light,
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark),
brightness: Brightness.dark,
),
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
home: const HomeScreen(),
);
}
}
这里把 theme 和 darkTheme 都先放好,后面切主题不会推倒重来。isDarkMode 现在是静态值,写法不漂亮,但够直接;等状态管理接上来,再把它替换掉就行。
lib/widgets/app_bar_title.dart
import 'package:flutter/material.dart';
class AppBarTitle extends StatelessWidget {
final String title;
const AppBarTitle({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Text(
title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
);
}
}
把 AppBar 标题抽成独立组件,属于那种现在看着不起眼、后面能少改很多次的处理。它不是为了炫技,只是为了让相同样式别在各个页面里重复一遍。
lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import '../widgets/app_bar_title.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const AppBarTitle(title: 'My App'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Welcome to My App v1.0!', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
const SizedBox(height: 24),
const Text('You have pushed the button this many times:'),
Text('$_counter', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: _incrementCounter,
icon: const Icon(Icons.add),
label: const Text('Increment'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}


