【Flutter for OpenHarmony实战】全局状态管理架构设计:企业级应用最佳实践

前言

在这里插入图片描述

记得刚开始做Flutter项目的时候,状态管理是一团乱麻。数据散落在各个Widget中,传递起来费劲又容易出错。

后来我学会了Provider,但问题又来了:哪些状态应该放在全局?怎么组织Provider?Provider之间怎么通信?

我也踩过不少坑。比如把所有状态都塞到一个Provider里,结果臃肿得难以维护。或者Provider嵌套太深,读取数据要写好多层context。

经过多次实践,我总结出了一套全局状态管理的方案。这篇文章我想分享全局状态管理的架构设计思路,以及实际项目中的最佳实践。


一、全局状态规划原则

1.1 什么样的状态需要全局管理?

不是所有状态都需要全局管理。我的判断标准是:

需要全局管理的状态

  • 用户信息(登录状态、用户资料)
  • 应用配置(主题、语言)
  • 业务数据(购物车、订单列表)
  • 缓存数据

不需要全局管理的状态

  • UI临时状态(按钮加载状态)
  • 局部组件状态(开关、输入框内容)
  • 一次性数据(弹窗显示状态)

经验总结:能用局部解决就用局部,全局状态要慎重使用。

1.2 状态分类体系

类型 示例 特点 管理方式
用户状态 登录信息、权限 应用级别,贯穿整个生命周期 全局Provider
业务状态 购物车、订单 特定业务流程相关 模块Provider
配置状态 主题、语言 用户偏好设置 全局Provider
缓存状态 接口数据、离线数据 提升性能和体验 混合方案
UI状态 加载中、展开/收起 局部临时状态 StatefulWidget

1.3 状态设计原则

单一职责原则:一个Provider只负责一类状态

// 好的做法
class AuthProvider extends ChangeNotifier { }
class CartProvider extends ChangeNotifier { }
class ThemeProvider extends ChangeNotifier { }

// 不好的做法
class AppProvider extends ChangeNotifier {
  // 用户、购物车、主题全混在一起
}

最小化原则:只暴露必要的数据和方法

class CartProvider extends ChangeNotifier {
  final List<CartItem> _items = [];

  // 只暴露getter,不暴露内部列表
  List<CartItem> get items => List.unmodifiable(_items);

  // 提供操作方法,而不是直接修改
  void addItem(CartItem item) { }
  void removeItem(String id) { }
}

二、Provider架构设计方案

2.1 MultiProvider统一注册

在应用根部统一注册所有Provider:

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => AuthProvider()),
        ChangeNotifierProvider(create: (_) => CartProvider()),
        ChangeNotifierProvider(create: (_) => ThemeProvider()),
        ChangeNotifierProvider(create: (_) => NotificationProvider()),
      ],
      child: MaterialApp(...),
    );
  }
}

好处

  1. 所有Provider集中管理,一目了然
  2. 方便在应用启动时初始化数据
  3. 便于Provider之间的依赖管理

2.2 Provider层级设计

MaterialApp
├── AuthProvider (全局,用户相关)
├── ThemeProvider (全局,主题相关)
├── NotificationProvider (全局,通知相关)
└── CartProvider (业务相关,可以放得更浅)
    └── 某些页面

建议

  • 真正全局的Provider(用户、主题)放在MaterialApp层
  • 业务相关的Provider可以放在具体页面或模块中

2.3 Provider初始化策略

class AuthProvider extends ChangeNotifier {
  AuthProvider() {
    _initializeFromStorage();
  }

  Future<void> _initializeFromStorage() async {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString('token');
    if (token != null) {
      _isLoggedIn = true;
      // 加载用户信息
    }
  }
}

注意:Provider的create方法会立即执行,所以要避免耗时操作。如果需要异步初始化,考虑使用FutureProvider或初始化方法。


三、各模块状态实现

3.1 认证状态

在这里插入图片描述
在这里插入图片描述

class AuthProvider extends ChangeNotifier {
  bool _isLoggedIn = false;
  String _userName = '';
  String _userEmail = '';
  DateTime? _loginTime;

  // Getters
  bool get isLoggedIn => _isLoggedIn;
  String get userName => _userName;
  String get userEmail => _userEmail;
  DateTime? get loginTime => _loginTime;

  // 登录
  Future<void> login(String name, String email) async {
    // 调用登录API
    // 保存token
    _userName = name;
    _userEmail = email;
    _isLoggedIn = true;
    _loginTime = DateTime.now();
    notifyListeners();
  }

  // 退出
  Future<void> logout() async {
    // 清除token
    _isLoggedIn = false;
    _userName = '';
    _userEmail = '';
    _loginTime = null;
    notifyListeners();
  }

  // 检查权限
  bool hasPermission(String permission) {
    // 实现权限检查逻辑
    return true;
  }
}

3.2 购物车状态

在这里插入图片描述

class CartItem {
  final String id;
  final String name;
  final double price;
  int quantity;

  CartItem({
    required this.id,
    required this.name,
    required this.price,
    this.quantity = 1,
  });

  CartItem copyWith({int? quantity}) {
    return CartItem(
      id: id,
      name: name,
      price: price,
      quantity: quantity ?? this.quantity,
    );
  }
}

class CartProvider extends ChangeNotifier {
  final List<CartItem> _items = [];

  // 只读访问
  List<CartItem> get items => List.unmodifiable(_items);
  int get itemCount => _items.fold(0, (sum, item) => sum + item.quantity);
  double get totalPrice =>
      _items.fold(0, (sum, item) => sum + item.price * item.quantity);

  // 操作方法
  void addItem(CartItem item) {
    final index = _items.indexWhere((i) => i.id == item.id);
    if (index >= 0) {
      _items[index] = item.copyWith(quantity: _items[index].quantity + 1);
    } else {
      _items.add(item);
    }
    notifyListeners();
  }

  void removeItem(String id) {
    final index = _items.indexWhere((i) => i.id == id);
    if (index >= 0) {
      if (_items[index].quantity > 1) {
        _items[index] = _items[index].copyWith(quantity: _items[index].quantity - 1);
      } else {
        _items.removeAt(index);
      }
      notifyListeners();
    }
  }

  void clear() {
    _items.clear();
    notifyListeners();
  }
}

3.3 主题状态

在这里插入图片描述

class ThemeProvider extends ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.system;
  Color _primaryColor = Colors.blue;

  ThemeMode get themeMode => _themeMode;
  Color get primaryColor => _primaryColor;

  ThemeData get lightTheme => ThemeData(
    useMaterial3: true,
    brightness: Brightness.light,
    colorScheme: ColorScheme.fromSeed(
      seedColor: _primaryColor,
      brightness: Brightness.light,
    ),
  );

  ThemeData get darkTheme => ThemeData(
    useMaterial3: true,
    brightness: Brightness.dark,
    colorScheme: ColorScheme.fromSeed(
      seedColor: _primaryColor,
      brightness: Brightness.dark,
    ),
  );

  void setThemeMode(ThemeMode mode) {
    _themeMode = mode;
    notifyListeners();
  }

  void setPrimaryColor(Color color) {
    _primaryColor = color;
    notifyListeners();
  }
}

3.4 通知状态

在这里插入图片描述

enum NotificationType { info, success, warning, error }

class NotificationItem {
  final NotificationType type;
  final String title;
  final String message;
  final DateTime timestamp;
  bool isRead;

  NotificationItem({
    required this.type,
    required this.title,
    required this.message,
    required this.timestamp,
    this.isRead = false,
  });
}

class NotificationProvider extends ChangeNotifier {
  final List<NotificationItem> _notifications = [];

  List<NotificationItem> get notifications => List.unmodifiable(_notifications);
  int get unreadCount => _notifications.where((n) => !n.isRead).length;

  void addNotification(NotificationItem notification) {
    _notifications.insert(0, notification);
    notifyListeners();
  }

  void markAsRead(NotificationItem notification) {
    notification.isRead = true;
    notifyListeners();
  }

  void markAllAsRead() {
    for (var notification in _notifications) {
      notification.isRead = true;
    }
    notifyListeners();
  }

  void clearAll() {
    _notifications.clear();
    notifyListeners();
  }
}

四、状态持久化方案

4.1 使用shared_preferences

class AuthProvider extends ChangeNotifier {
  static const String _tokenKey = 'auth_token';
  static const String _userNameKey = 'user_name';

  Future<void> _loadFromStorage() async {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString(_tokenKey);
    final name = prefs.getString(_userNameKey);

    if (token != null && name != null) {
      _token = token;
      _userName = name;
      _isLoggedIn = true;
      notifyListeners();
    }
  }

  Future<void> _saveToStorage() async {
    final prefs = await SharedPreferences.getInstance();
    if (_isLoggedIn) {
      await prefs.setString(_tokenKey, _token!);
      await prefs.setString(_userNameKey, _userName);
    } else {
      await prefs.remove(_tokenKey);
      await prefs.remove(_userNameKey);
    }
  }
}

4.2 数据序列化

class CartItem {
  // ... 其他代码

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'price': price,
      'quantity': quantity,
    };
  }

  factory CartItem.fromJson(Map<String, dynamic> json) {
    return CartItem(
      id: json['id'],
      name: json['name'],
      price: json['price'].toDouble(),
      quantity: json['quantity'],
    );
  }
}

class CartProvider extends ChangeNotifier {
  Future<void> _saveCart() async {
    final prefs = await SharedPreferences.getInstance();
    final cartJson = jsonEncode(_items.map((i) => i.toJson()).toList());
    await prefs.setString('cart', cartJson);
  }

  Future<void> _loadCart() async {
    final prefs = await SharedPreferences.getInstance();
    final cartJson = prefs.getString('cart');
    if (cartJson != null) {
      final List<dynamic> decoded = jsonDecode(cartJson);
      _items.clear();
      _items.addAll(decoded.map((json) => CartItem.fromJson(json)));
      notifyListeners();
    }
  }
}

五、Provider间通信与协同

5.1 通过事件总线通信

// Provider A 发送事件
void someAction() {
  eventBus.fire(CartUpdatedEvent());
}

// Provider B 监听事件
class CartProvider extends ChangeNotifier {
  StreamSubscription? _subscription;

  CartProvider() {
    _subscription = eventBus.on<CartUpdatedEvent>().listen((event) {
      // 处理事件
    });
  }

  
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }
}

5.2 直接引用方式

class CartProvider extends ChangeNotifier {
  final AuthProvider _authProvider;

  CartProvider(this._authProvider);

  void checkout() {
    if (!_authProvider.isLoggedIn) {
      // 提示登录
      return;
    }
    // 执行结算
  }
}

// 注册时注入依赖
MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => AuthProvider()),
    ChangeNotifierProxyProvider<AuthProvider, CartProvider>(
      create: (_) => CartProvider(context.read<AuthProvider>()),
      update: (_, auth, previous) =>
          previous ?? CartProvider(auth),
    ),
  ],
)

六、性能优化策略

6.1 避免过度嵌套

// 不好的做法 - 嵌套太深
Provider<AuthProvider>(
  create: (_) => AuthProvider(),
  child: Provider<CartProvider>(
    create: (_) => CartProvider(),
    child: Provider<ThemeProvider>(
      create: (_) => ThemeProvider(),
      child: MaterialApp(...),
    ),
  ),
)

// 好的做法 - 使用MultiProvider
MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => AuthProvider()),
    ChangeNotifierProvider(create: (_) => CartProvider()),
    ChangeNotifierProvider(create: (_) => ThemeProvider()),
  ],
  child: MaterialApp(...),
)

6.2 合理使用Consumer和Selector

// 简单场景用context.watch
Widget build(BuildContext context) {
  final cart = context.watch<CartProvider>();
  return Text('商品数:${cart.itemCount}');
}

// 需要优化性能用Selector
Selector<CartProvider, int>(
  selector: (context, cart) => cart.itemCount,
  builder: (context, count, child) {
    return Text('商品数:$count');
  },
)

// 需要局部重建用Consumer
Column(
  children: [
    Text('固定内容'),
    Consumer<CartProvider>(
      builder: (context, cart, child) {
        return Text('商品数:${cart.itemCount}');
      },
    ),
  ],
)

6.3 性能优化检查清单

  • 使用Selector精准订阅
  • 避免在build中创建对象
  • 使用const构造函数
  • 合理划分Provider
  • Provider放在合适的位置

总结

全局状态管理是Flutter应用架构的重要组成部分,设计得好可以让代码更清晰、更易维护。

核心要点:

  1. 合理规划哪些状态需要全局管理
  2. 按功能模块划分Provider,单一职责
  3. 使用MultiProvider统一注册管理
  4. 注意状态持久化和数据序列化
  5. 合理使用Consumer和Selector优化性能
  6. 避免过度嵌套和过度设计

状态管理方案选择:

  • 小型项目:Provider + ChangeNotifier
  • 中型项目:Provider + 一些自定义优化
  • 大型项目:考虑Riverpod、Bloc等更强大的方案

记住,没有最好的方案,只有最合适的。根据项目实际情况选择合适的方案才是明智的。

欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐