AtomGit Flutter鸿蒙客户端:Provider状态管理

架构分层

项目的状态管理分为三层:

复制代码
MultiProvider(应用根节点)
├── Provider<AtomGitApiClient>        --- 全局服务
├── ChangeNotifierProvider<AuthProvider> --- 全局状态
│
└── 各页面(按需创建)
    ├── ChangeNotifierProvider<RepoDetailProvider>
    ├── ChangeNotifierProvider<CodeProvider>
    ├── ChangeNotifierProvider<IssueProvider>
    └── ...

全局 Provider

MaterialApp 之上通过 MultiProvider 注入:

dart 复制代码
class AtomGitApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider<AtomGitApiClient>(
          create: (_) => AtomGitApiClient(),
        ),
        ChangeNotifierProvider<AuthProvider>(
          create: (_) => AuthProvider(
            apiClient: /* 引用上面的 ApiClient */,
          )..tryRestoreSession(),
        ),
      ],
      child: MaterialApp(/* ... */),
    );
  }
}
Provider 类型 生命周期 用途
AtomGitApiClient Provider(不变) App 级别 HTTP 客户端,被所有页面注入
AuthProvider ChangeNotifierProvider App 级别 登录状态,驱动 Tab UI 切换

Provider<T> 用于不变的服务对象。ChangeNotifierProvider<T> 用于会变化、需要通知 UI 的状态。

页面级 Provider

每个详情页在 build 中创建自己的 Provider:

dart 复制代码
class RepoDetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args =
        ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;

    return ChangeNotifierProvider(
      create: (_) =>
          RepoDetailProvider(context.read<AtomGitApiClient>())
            ..load(args['owner'], args['name']),
      child: _RepoDetailBody(/* ... */),
    );
  }
}

关键模式:create 中完成两件事:

  1. 通过 context.read<AtomGitApiClient>() 获取全局 ApiClient
  2. 通过 ..load() 级联语法立即触发数据加载

Provider 的生命周期与页面绑定:页面被 Navigator.pop 移除时,Provider 自动 dispose。

读取 Provider 的三种方式

context.read() --- 一次性读取

dart 复制代码
// 在 create/onTap 等回调中,不需要监听
final apiClient = context.read<AtomGitApiClient>();
provider.loadMore();

不会导致重建,适合事件处理函数内使用。

context.watch() --- 持续监听

dart 复制代码
// 在 build 方法中,需要随状态变化重建
final isLoggedIn = context.watch<AuthProvider>().isLoggedIn;

每当 Provider 调用 notifyListeners(),Widget 就会重建。适合 build 方法内使用。

Consumer --- 局部监听

dart 复制代码
ChangeNotifierProvider.value(
  value: _userProvider!,
  child: Consumer<UserProvider>(
    builder: (context, provider, _) {
      return Text('仓库: ${provider.user?.publicRepos}');
    },
  ),
);

只有 Consumer 包裹的部分会重建,外层 Widget 不受影响。适合局部优化。

标准 Provider 模板

所有 Provider 遵循统一的加载模式:

dart 复制代码
class SomeProvider extends ChangeNotifier {
  final AtomGitApiClient _apiClient;

  List<Item> _items = [];
  bool _isLoading = false;
  String? _error;
  bool _hasMore = false;
  int _page = 1;

  // Getters
  List<Item> get items => List.unmodifiable(_items);
  bool get isLoading => _isLoading;
  String? get error => _error;
  bool get hasMore => _hasMore;

  Future<void> load() async {
    _page = 1;
    _isLoading = true;
    _error = null;
    notifyListeners();           // ① 通知:开始加载

    try {
      final response = await _apiClient.get(/* ... */);
      _items = /* parse response */;
      _hasMore = _items.length >= 30;
    } on ApiException catch (e) {
      _error = e.message;       // ② 区分 API 异常
    } catch (e) {
      _error = '加载失败';        // ③ 兜底错误
    } finally {
      _isLoading = false;
      notifyListeners();           // ④ 通知:加载完成
    }
  }
}

四次 notifyListeners() 的节奏:

  1. 加载前:设置 loading 状态,UI 展示加载指示器
  2. 加载后(finally):清除 loading,UI 展示数据/错误

UI 状态模式

每个页面的 buildBody 方法遵循三态逻辑:

dart 复制代码
Widget _buildBody(SomeProvider provider) {
  // 优先级 1:错误(且无缓存数据)
  if (provider.error != null && provider.items.isEmpty) {
    return ErrorRetryWidget(
      message: provider.error!,
      onRetry: () => provider.load(),
    );
  }

  // 优先级 2:加载中
  if (provider.isLoading && provider.items.isEmpty) {
    return const LoadingIndicator(message: '加载中...');
  }

  // 优先级 3:空结果
  if (provider.items.isEmpty) {
    return const Center(child: Text('暂无数据'));
  }

  // 优先级 4:正常数据
  return ListView.builder(/* ... */);
}

错误优先于加载中优先于空结果优先于正常数据 ------ 这是判断链的顺序。

AuthProvider --- 全局状态驱动

AuthProvider 是唯一贯穿全应用的状态:

dart 复制代码
class AuthProvider extends ChangeNotifier {
  final AtomGitApiClient _apiClient;
  bool _isLoggedIn = false;

  bool get isLoggedIn => _isLoggedIn;

  Future<void> tryRestoreSession() async {
    final token = await LocalStorage.instance.read<String>('access_token');
    if (token != null && token.isNotEmpty) {
      _apiClient.setAccessToken(token);
      _isLoggedIn = true;
      notifyListeners();
    }
  }

  Future<void> setTokenFromManualInput(String token) async {
    _apiClient.setAccessToken(token);
    await LocalStorage.instance.write('access_token', token);
    _isLoggedIn = true;
    notifyListeners();
  }

  Future<void> logout() async {
    _apiClient.setAccessToken(null);
    await LocalStorage.instance.delete('access_token');
    _isLoggedIn = false;
    notifyListeners();
  }
}

登录/登出后的连锁反应依赖 Provider 的广播机制:

复制代码
AuthProvider.notifyListeners()
  → 所有 context.watch<AuthProvider>() 的 Widget 重建
  → MainShell 底部导航切换显示
  → ProfileTab 创建/销毁 UserProvider
  → HomeTab 切换数据源
  → NotificationsTab 切换登录引导/占位

不需要手动通知各个组件,Provider 自动处理依赖传播。

changeNotifierProvider.value vs create

dart 复制代码
// 标准用法:Provider 由 ChangeNotifierProvider 创建和 dispose
ChangeNotifierProvider(
  create: (_) => SomeProvider(),
  child: _Body(),
)

// value 用法:Provider 已存在(手动管理生命周期)
ChangeNotifierProvider.value(
  value: existingProvider,
  child: Consumer<SomeProvider>(/* ... */),
)

ProfileTab 使用 .value 的原因是它手动管理 UserProvider 的创建/销毁(跟随登录状态),不能交给 create 自动处理。

避免嵌套地狱

通过 Provider 的架构,UI 代码不直接持有数据加载逻辑:

dart 复制代码
// 不这样写:UI 中直接调 API
class _BodyState extends State<_Body> {
  List<Repository> _repos = [];
  bool _loading = false;

  Future<void> _load() async {
    setState(() => _loading = true);
    final resp = await http.get(/* ... */);
    setState(() { _repos = /* ... */; _loading = false; });
  }
}

// 而是这样写:Provider 封装数据逻辑
class _BodyState extends State<_Body> {
  @override
  Widget build(BuildContext context) {
    final provider = context.watch<RepoDetailProvider>();
    if (provider.isLoading) return LoadingIndicator();
    return ListView(/* provider.repositories */);
  }
}

Provider 将异步加载、错误处理、分页状态从 Widget 的 State 中剥离,Widget 只关心"当前应该展示什么"。

相关推荐
伶俜661 小时前
# [特殊字符] 零基础学 ArkUI 数据持久化(专题三):5 种存储方案深度对比
学习·华为·wpf·harmonyos
FrameNotWork1 小时前
HarmonyOS6.1 图像分类应用完整实战:从模型到界面
人工智能·分类·数据挖掘·harmonyos
MemoriKu2 小时前
Flutter 相册 APP 视频模态稳定化实战:从视频抽帧、Embedding 元数据到 Android 真机启动修复
android·开发语言·前端·flutter·架构·音视频·embedding
nice先生的狂想曲2 小时前
flutter页面滚动TabBar+TabBarView
flutter·客户端
带刺的坐椅2 小时前
SolonCode(编码智能体)支持鸿蒙 PC
java·web·ai编程·harmonyos·soloncode·鸿蒙 pc
李二。2 小时前
HarmonyOS NEXT 定时关机工具:从设计到实现的完整技术解析
华为·harmonyos
川石课堂软件测试2 小时前
UI自动化测试|CSS元素定位实践
css·测试工具·ui·fiddler·单元测试·appium·harmonyos
yuegu7772 小时前
HarmonyOS应用<节气通>开发第15篇:学习记录页面
学习·信息可视化·harmonyos