跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Dart大前端

从零构建可扩展 Flutter 应用:v1.0 到 v2.0 架构演进详解

Flutter 应用初期架构缺失常导致后期维护困难。本文从最小可运行版本出发,演示如何逐步引入 Riverpod 状态管理与本地持久化,完成从 v1.0 基础骨架到 v2.0 可扩展架构的演进。通过模块化目录划分、ProviderScope 包裹及 StateNotifier 实现 UI 与逻辑解耦,确保项目具备深色模式切换、数据持久化及未来扩展能力,为生产级开发提供清晰起点。

孤勇者发布于 2026/3/22更新于 2026/5/54 浏览
从零构建可扩展 Flutter 应用:v1.0 到 v2.0 架构演进详解

从零构建可扩展 Flutter 应用:v1.0 到 v2.0 架构演进详解

在 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 —— 应用入口

import 'package:flutter/material.dart';
import 'app.dart';

void main() {
  runApp(const MyApp());
}

说明:main() 是 Dart 程序入口。调用 runApp() 启动 Flutter 应用。不在此处写业务逻辑,仅负责启动根组件 MyApp,符合单一职责原则。

2. lib/app.dart —— App 根组件

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 —— 可复用组件

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 —— 主页面

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),
      ),
    );
  }
}

说明:使用 StatefulWidget 管理局部计数器状态。_counter 是私有变量,通过 setState 触发 UI 更新。页面结构清晰:AppBar + Body + FloatingActionButton。局限性在于状态无法跨页面共享,重启后丢失——这正是 v2.0 要解决的问题。

第二阶段:v2.0 —— 引入 Riverpod,实现可扩展架构

升级目标

  • 状态全局可访问
  • 支持深色/浅色/系统主题动态切换
  • 计数器数据持久化(重启不丢失)
  • 解耦 UI 与逻辑

新增依赖(pubspec.yaml)

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.1 # 状态管理
  shared_preferences: ^2.2.2 # 本地存储

运行命令 flutter pub get 安装依赖。

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

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(
    child: MyApp(),
  ));
}

说明:ProviderScope 是 Riverpod 的上下文提供者,必须包裹整个应用。WidgetsFlutterBinding.ensureInitialized() 是使用平台通道(如 SharedPreferences)的必要步骤。

2. lib/providers/app_theme_provider.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 —— 持久化计数器

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 —— 主题切换按钮

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: [
              const Icon(Icons.light_mode, color: Colors.yellow[700]),
              const SizedBox(width: 8),
              const Text('Light'),
            ],
          ),
        ),
        PopupMenuItem(
          value: AppThemeMode.dark,
          child: Row(
            children: [
              const Icon(Icons.dark_mode, color: Colors.blue[300]),
              const SizedBox(width: 8),
              const Text('Dark'),
            ],
          ),
        ),
        PopupMenuItem(
          value: AppThemeMode.system,
          child: Row(
            children: [
              const 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 主题

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 状态

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 只需:

// 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 中优雅处理加载/错误/数据状态:

ref.watch(weatherProvider).when(
  data: (weather) => Text('${weather.temp}°C'),
  loading: () => CircularProgressIndicator(),
  error: (err, _) => Text('Failed to load'),
);

总结

  • v1.0 是一个规范、可交付的起点,避免'脏快糙'陷阱。
  • v2.0 通过 Riverpod + 模块化设计,实现了状态集中管理、UI 逻辑解耦、数据持久化。
  • 所有代码均可直接复制使用,并具备良好的扩展性。

好的架构不是一开始就复杂的,而是从第一天起就为'变化'做好准备。

附录:完整文件清单

你可以按以下顺序创建文件:

  1. lib/main.dart
  2. lib/app.dart
  3. lib/constants/app_colors.dart(可选)
  4. lib/providers/app_theme_provider.dart
  5. lib/providers/counter_provider.dart
  6. lib/screens/home_screen.dart
  7. lib/widgets/app_bar_title.dart
  8. lib/widgets/theme_toggle_button.dart
  9. lib/utils/logger.dart(可留空)

然后更新 pubspec.yaml 并运行 flutter pub get。

目录

  1. 从零构建可扩展 Flutter 应用:v1.0 到 v2.0 架构演进详解
  2. 第一阶段:v1.0 —— 干净的基础骨架
  3. 目标
  4. 项目结构
  5. 1. lib/main.dart —— 应用入口
  6. 2. lib/app.dart —— App 根组件
  7. 3. lib/widgets/appbartitle.dart —— 可复用组件
  8. 4. lib/screens/home_screen.dart —— 主页面
  9. 第二阶段:v2.0 —— 引入 Riverpod,实现可扩展架构
  10. 升级目标
  11. 新增依赖(pubspec.yaml)
  12. v2.0 项目结构
  13. 1. lib/main.dart —— 包裹 ProviderScope
  14. 2. lib/providers/appthemeprovider.dart —— 主题状态管理
  15. 3. lib/providers/counter_provider.dart —— 持久化计数器
  16. 4. lib/widgets/themetogglebutton.dart —— 主题切换按钮
  17. 5. lib/app.dart —— 使用 Riverpod 主题
  18. 6. lib/screens/home_screen.dart —— 使用 Riverpod 状态
  19. 未来扩展路径
  20. 总结
  21. 附录:完整文件清单
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 地瓜机器人 RDK 系列选型指南:X3 vs X5 vs S100 vs S100P
  • 通义万相 2.1 实战:AIGC 内容生成与多模态应用指南
  • AI 可得性时代:人类能力结构的重构与未来应对
  • AIGC 技术解析:市场现状、应用场景与未来趋势
  • Python 百度搜索接口封装与使用指南
  • AI 原生低代码平台的技术架构与核心能力解析
  • 国外主流网络安全与黑客技术论坛及资源导航
  • GitHub Copilot、Trae 与 Cursor 三款 AI 编程工具对比分析
  • MambaRefine-YOLO:一种用于无人机影像的双模态小目标检测器
  • VisionTransformer(ViT)在时间序列行为识别中的应用
  • 字节跳动前端一面面经:核心原理与性能排查实战
  • Java AI 编程实测:从自然语言生成完整工程,初中级开发者效率分析
  • VisionTransformer(ViT)在时间序列行为识别中的应用
  • 5 款开源 PPT 生成大模型实测对比:技术原理与工具评测
  • VSCode 精准禁用 Copilot 代码补全:按语言与场景配置
  • RabbitMQ 事务机制与消息限流实战详解
  • 2024 年 5 款主流 AI 编码工具对比与选型指南
  • 本地使用ComfyUI运行Stable Diffusion 3.5
  • Vue3 前端开发配置:VSCode settings.json 与 .prettierrc 设置
  • C++ KMP 算法详解:高效字符串查找实现

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online