
-
个人首页: VON
-
鸿蒙系列专栏: 鸿蒙开发小型案例总结
-
综合案例 :鸿蒙综合案例开发
-
鸿蒙6.0:从0开始的开源鸿蒙6.0.0
-
鸿蒙5.0:鸿蒙5.0零基础入门到项目实战
-
Electron适配开源鸿蒙专栏:Electron for OpenHarmony
-
本文章所属专栏:Flutter for OpenHarmony
-
文章AtomGit地址:Template_V2.0
v1.0 → v2.0 全代码详解
- [从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解](#从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解)
-
- [🧱 第一阶段:v1.0 ------ 干净的基础骨架](#🧱 第一阶段:v1.0 —— 干净的基础骨架)
-
- [✅ 目标](#✅ 目标)
- [📁 项目结构](#📁 项目结构)
- [1. `lib/main.dart` ------ 应用入口](#1.
lib/main.dart—— 应用入口) - [2. `lib/app.dart` ------ App 根组件](#2.
lib/app.dart—— App 根组件) - [3. `lib/widgets/app_bar_title.dart` ------ 可复用组件](#3.
lib/widgets/app_bar_title.dart—— 可复用组件) - [4. `lib/screens/home_screen.dart` ------ 主页面](#4.
lib/screens/home_screen.dart—— 主页面)
- [⚙️ 第二阶段:v2.0 ------ 引入 Riverpod,实现可扩展架构](#⚙️ 第二阶段:v2.0 —— 引入 Riverpod,实现可扩展架构)
-
- [✅ 升级目标](#✅ 升级目标)
- [📦 新增依赖(`pubspec.yaml`)](#📦 新增依赖(
pubspec.yaml)) - [📁 v2.0 项目结构](#📁 v2.0 项目结构)
- [1. `lib/main.dart` ------ 包裹 ProviderScope](#1.
lib/main.dart—— 包裹 ProviderScope) - [2. `lib/providers/app_theme_provider.dart` ------ 主题状态管理](#2.
lib/providers/app_theme_provider.dart—— 主题状态管理) - [3. `lib/providers/counter_provider.dart` ------ 持久化计数器](#3.
lib/providers/counter_provider.dart—— 持久化计数器) - [4. `lib/widgets/theme_toggle_button.dart` ------ 主题切换按钮](#4.
lib/widgets/theme_toggle_button.dart—— 主题切换按钮) - [5. `lib/app.dart` ------ 使用 Riverpod 主题](#5.
lib/app.dart—— 使用 Riverpod 主题) - [6. `lib/screens/home_screen.dart` ------ 使用 Riverpod 状态](#6.
lib/screens/home_screen.dart—— 使用 Riverpod 状态)
- [🔮 未来扩展路径](#🔮 未来扩展路径)
- [✅ 总结](#✅ 总结)
- [📥 附录:完整文件清单](#📥 附录:完整文件清单)

从零构建可扩展 Flutter 应用:v1.0 → v2.0 全代码详解
副标题:一个干净、模块化、支持 Riverpod 状态管理的生产级起点
在 Flutter 开发中,很多项目失败并非因为技术不行,而是初期架构缺失 。本文将手把手带你从 最简可行应用(v1.0) 出发,逐步演进到 支持状态管理、主题切换、本地持久化的 v2.0 架构 ,并附上全部代码 + 逐行解释,助你打造一个真正面向未来的 Flutter 应用。
🧱 第一阶段:v1.0 ------ 干净的基础骨架
✅ 目标
- 最小可运行应用
- 标准 Material Design 结构
- 清晰目录划分
- 无第三方依赖
📁 项目结构
lib/
├── main.dart
├── app.dart
├── screens/home_screen.dart
└── widgets/app_bar_title.dart
1. lib/main.dart ------ 应用入口
dart
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(const MyApp());
}
说明:
main()是 Dart 程序入口。- 调用
runApp()启动 Flutter 应用。- 不在此处写业务逻辑 ,仅负责启动根组件
MyApp,符合单一职责原则。
2. lib/app.dart ------ 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(),
);
}
}
说明:
- 使用
MaterialApp提供 Material Design 支持。- 定义
theme和darkTheme,为深色模式做准备。home指向主页面,路由系统预留位置 (后续可替换为routes或GoRouter)。isDarkMode是静态变量,便于未来替换为状态管理驱动的动态值。
3. lib/widgets/app_bar_title.dart ------ 可复用组件
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 标题封装为独立 widget,提高复用性。
- 使用
Theme.of(context)获取当前主题样式,自动适配深浅色。- 通过
required this.title强制传参,避免空值错误。
4. lib/screens/home_screen.dart ------ 主页面
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),
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),
),
);
}
}
说明:
- 使用
StatefulWidget管理局部计数器状态。_counter是私有变量,通过setState触发 UI 更新。- 页面结构清晰:AppBar + Body + FloatingActionButton。
- 局限性:状态无法跨页面共享,重启后丢失------这正是 v2.0 要解决的问题。
⚙️ 第二阶段:v2.0 ------ 引入 Riverpod,实现可扩展架构
✅ 升级目标
- 状态全局可访问
- 支持深色/浅色/系统主题动态切换
- 计数器数据持久化(重启不丢失)
- 解耦 UI 与逻辑
📦 新增依赖(pubspec.yaml)
yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1 # 状态管理
shared_preferences: ^2.2.2 # 本地存储
运行命令:
bashflutter pub add flutter_riverpod shared_preferences
这里也可以直接进行保存

📁 v2.0 项目结构
lib/
├── main.dart
├── app.dart
├── constants/app_colors.dart (可选)
├── providers/
│ ├── app_theme_provider.dart
│ └── counter_provider.dart
├── screens/home_screen.dart
├── widgets/
│ ├── app_bar_title.dart
│ └── theme_toggle_button.dart
└── utils/logger.dart (预留)

1. lib/main.dart ------ 包裹 ProviderScope
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // 必须在使用 shared_preferences 前调用
runApp(
const ProviderScope( // Riverpod 的根容器
child: MyApp(),
),
);
}
说明:
ProviderScope是 Riverpod 的上下文提供者,必须包裹整个应用。WidgetsFlutterBinding.ensureInitialized()是使用平台通道(如 SharedPreferences)的必要步骤。
2. lib/providers/app_theme_provider.dart ------ 主题状态管理
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
enum AppThemeMode { light, dark, system }
// 用户选择的主题模式(可持久化)
final appThemeModeProvider = StateProvider<AppThemeMode>((ref) {
return AppThemeMode.light; // TODO: 未来从 SharedPreferences 读取
});
// 计算实际生效的 ThemeMode
final effectiveThemeModeProvider = Provider<ThemeMode>((ref) {
final mode = ref.watch(appThemeModeProvider);
switch (mode) {
case AppThemeMode.light: return ThemeMode.light;
case AppThemeMode.dark: return ThemeMode.dark;
case AppThemeMode.system: return ThemeMode.system;
}
});
说明:
StateProvider用于简单状态(如枚举、bool、int)。effectiveThemeModeProvider是一个派生状态 ,根据用户选择计算出ThemeMode。- 未来可轻松扩展 :将初始值从
SharedPreferences读取,实现"记住用户偏好"。
3. lib/providers/counter_provider.dart ------ 持久化计数器
dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0) {
_loadFromPrefs(); // 初始化时加载
}
Future<void> _loadFromPrefs() async {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getInt('counter') ?? 0;
state = saved; // 触发 UI 更新
}
Future<void> increment() async {
state++; // 自动通知监听者
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', state); // 保存
}
Future<void> reset() async {
state = 0;
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', state);
}
}
说明:
StateNotifierProvider适合管理复杂状态逻辑(如异步、验证、副作用)。state = newValue会自动通知所有监听者重建 UI。- 数据在
increment()和reset()中自动持久化到设备存储。
4. lib/widgets/theme_toggle_button.dart ------ 主题切换按钮
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/app_theme_provider.dart';
class ThemeToggleButton extends ConsumerWidget {
const ThemeToggleButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentMode = ref.watch(appThemeModeProvider);
return PopupMenuButton<AppThemeMode>(
onSelected: (mode) => ref.read(appThemeModeProvider.notifier).state = mode,
itemBuilder: (context) => [
PopupMenuItem(
value: AppThemeMode.light,
child: Row(children: [
Icon(Icons.light_mode, color: Colors.yellow[700]),
const SizedBox(width: 8),
const Text('Light'),
]),
),
PopupMenuItem(
value: AppThemeMode.dark,
child: Row(children: [
Icon(Icons.dark_mode, color: Colors.blue[300]),
const SizedBox(width: 8),
const Text('Dark'),
]),
),
PopupMenuItem(
value: AppThemeMode.system,
child: Row(children: [
Icon(Icons.settings, color: Colors.grey),
const SizedBox(width: 8),
const Text('System'),
]),
),
],
child: IconButton(
icon: currentMode == AppThemeMode.dark
? const Icon(Icons.dark_mode)
: const Icon(Icons.light_mode),
tooltip: 'Switch theme',
),
);
}
}
说明:
ConsumerWidget允许使用ref.watch监听状态变化。ref.read(...)用于写入状态(不监听变化,性能更好)。- 图标根据当前主题动态切换,提升用户体验。
5. lib/app.dart ------ 使用 Riverpod 主题
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'screens/home_screen.dart';
import 'providers/app_theme_provider.dart';
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(effectiveThemeModeProvider); // 监听主题变化
return MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData(useMaterial3: true, colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue)),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark),
),
themeMode: themeMode,
home: const HomeScreen(),
);
}
}
关键点:
MyApp从StatelessWidget变为ConsumerWidget,以便监听effectiveThemeModeProvider。- 当用户切换主题时,
MaterialApp自动重建,应用新主题。
6. lib/screens/home_screen.dart ------ 使用 Riverpod 状态
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/counter_provider.dart';
import '../widgets/app_bar_title.dart';
import '../widgets/theme_toggle_button.dart';
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // 自动监听计数器变化
return Scaffold(
appBar: AppBar(
title: const AppBarTitle(title: 'My App v2.0'),
centerTitle: true,
actions: [const ThemeToggleButton()], // 主题切换按钮
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Welcome to My App!', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
const SizedBox(height: 24),
const Text('Counter (persisted):'),
Text('$count', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: () => ref.read(counterProvider.notifier).increment(),
icon: const Icon(Icons.add),
label: const Text('Increment'),
),
TextButton.icon(
onPressed: () => ref.read(counterProvider.notifier).reset(),
icon: const Icon(Icons.refresh),
label: const Text('Reset'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
关键改进:
- 不再使用
StatefulWidget,UI 与状态完全解耦。ref.watch(counterProvider)自动更新计数显示。- 按钮通过
ref.read(...).increment()触发状态变更。- 重启应用后,计数器值依然保留!
测试


🔮 未来扩展路径
这个 v2.0 架构已为以下能力预留"插槽":
| 功能 | 扩展方式 |
|---|---|
| 多页面导航 | 添加 go_router,在 app.dart 中配置 |
| API 请求 | 创建 api_provider.dart,使用 FutureProvider |
| 用户登录 | 新增 auth_provider.dart,管理 Token 和用户信息 |
| 国际化 | 启用 flutter_localizations,添加 ARB 文件 |
| 日志监控 | 在 utils/logger.dart 中封装 Sentry/Firebase |
例如,添加天气 API 只需:
dart
// providers/weather_provider.dart
final weatherProvider = FutureProvider<Weather>((ref) async {
final res = await http.get(Uri.parse('https://api.weather.com/...'));
return Weather.fromJson(json.decode(res.body));
});
然后在 UI 中优雅处理加载/错误/数据状态:
dart
ref.watch(weatherProvider).when(
data: (weather) => Text('${weather.temp}°C'),
loading: () => CircularProgressIndicator(),
error: (err, _) => Text('Failed to load'),
);
✅ 总结
- v1.0 是一个规范、可交付的起点,避免"脏快糙"陷阱。
- v2.0 通过 Riverpod + 模块化设计,实现了状态集中管理、UI 逻辑解耦、数据持久化。
- 所有代码均可直接复制使用,并具备良好的扩展性。
好的架构不是一开始就复杂的,而是从第一天起就为"变化"做好准备。
📥 附录:完整文件清单
你可以按以下顺序创建文件:
lib/main.dartlib/app.dartlib/constants/app_colors.dart(可选)lib/providers/app_theme_provider.dartlib/providers/counter_provider.dartlib/screens/home_screen.dartlib/widgets/app_bar_title.dartlib/widgets/theme_toggle_button.dartlib/utils/logger.dart(可留空)
然后更新 pubspec.yaml 并运行 flutter pub get。