Flutter圈子Android进阶之路Android开发

Flutter之旅 -- 项目架构

2025-08-12  本文已影响0人  开心wonderful

本篇文章主要介绍以下几个知识点:

  • 分层架构设计
  • Provider vs Riverpod
  • Riverpod 在实际项目中的使用示例
Flutter之旅

1. 分层架构设计

Android 应用架构指南:https://developer.android.com/topic/architecture?hl=zh-cn
使用 Riverpod 的 Flutter 应用架构指南:https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/

1.1 Flutter 分层架构概述

分层架构

基于 Clean Architecture 原则,Flutter 应用采用分层架构确保关注点分离:

┌─────────────────────────────────────┐
│         Presentation Layer          │  ← UI 层
├─────────────────────────────────────┤
│         Application Layer           │  ← 业务逻辑层
├─────────────────────────────────────┤
│           Domain Layer              │  ← 领域层
├─────────────────────────────────────┤
│            Data Layer               │  ← 数据层
└─────────────────────────────────────┘

主要层次包括:

1.2 与 Android 架构对比

对比 Android 应用架构
层级 Flutter + Riverpod Android MVVM
表现层 Widget + Consumer View + Activity/Fragment
应用层 StateNotifier + Provider ViewModel + LiveData/Flow
领域层 Domain Models Use Cases(网域层,可选)
数据层 Repository + DataSource Repository + Room/Retrofit

相似点:

差异点:

2. Provider vs Riverpod

Provider 和 Riverpod 这两个库的作者都是 Remi Rousselet,新库命名是旧库的字母重排。

Provider 的优点是 简单易用,上手难度低,适用于应用规模较小,状态管理不太复杂的场景。

Provider 的局限如下:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter(1), key: ValueKey(1)),
        ChangeNotifierProvider(create: (_) => Counter(2), key: ValueKey(2)),
      ],
      child: MyApp(),
    ),
  );
}

// 通过key指定使用Counter实例
Provider.of<Counter>(context, listen: false, key: ValueKey(1)).increment();

Riverpod 在 Provider 的基础上进行重构,解决上述问题之余,提供了 更灵活/精细的状态管理机制,状态不可变,编译时类型安全、易于测试等特性,更清晰的代码组织和维护方式 (注解代码生成),可以有效的组织和管理大规模的状态。

选择 Riverpod 的理由:

  1. 类型安全: 编译时检查,减少运行时错误
  2. 更好的性能: 精确的重建控制,避免不必要的 Widget 重建
  3. 简化的 API: 更直观的语法,减少样板代码
  4. 强大的开发工具: 更好的调试和开发体验
  5. 测试友好: 更容易进行单元测试和集成测试

3. Riverpod 基本使用与核心原理

3.1 Riverpod 基本使用

Riverpod 使用详解可参考:https://juejin.cn/post/7359402114018689076
官方文档:https://riverpod.dev/docs/introduction/why_riverpod

Riverpod 提供了多种状态管理模式,适用于不同的场景:

模式 适用场景 优点 缺点
StateProvider 简单状态 语法简单,直接修改 不适合复杂逻辑
StateNotifier 复杂状态 强类型,业务逻辑封装好 需要更多代码
FutureProvider 一次性异步 自动处理加载状态 不适合可变异步操作
StreamProvider 持续数据流 自动处理流状态 需要管理流的生命周期
ChangeNotifier 兼容旧代码 兼容性好 性能相对较差
Notifier 现代化状态管理 语法简洁,性能好 需要代码生成

3.2 Riverpod 核心原理

把应用的“数据源”和“依赖关系”想象成一张河网:上游的水质变化,会沿着支流层层传导到下游。
Riverpod 做的事情,就是把这张“依赖河网”用代码表达出来,并且自动完成“缓存、传导、重算、清理”。
这让复杂异步场景变得简单,正如官方所说:它是一个“响应式缓存框架”,专注于缓存与自动刷新。

一个极简(伪)实现:

// 极简容器:保存缓存与依赖
class MiniContainer {
  final Map<Object, dynamic> _cache = {};
  final Map<Object, Set<Object>> _deps = {}; // provider -> its dependencies

  T read<T>(MiniProvider<T> provider) {
    if (_cache.containsKey(provider)) return _cache[provider] as T;

    final tracker = _DependencyTracker(this, provider);
    final value = provider.create(tracker);
    _cache[provider] = value;
    _deps[provider] = tracker.dependencies;
    return value;
  }

  // 当依赖变更时,向下游传播“需要重算”的信号
  void markDirty(Object changed) {
    for (final entry in _deps.entries) {
      if (entry.value.contains(changed)) {
        _cache.remove(entry.key); // 使下游失效,下一次 read 时重算
        markDirty(entry.key);
      }
    }
  }
}

class _DependencyTracker {
  final MiniContainer container;
  final Object owner;
  final Set<Object> dependencies = {};
  _DependencyTracker(this.container, this.owner);

  T watch<T>(MiniProvider<T> dep) {
    dependencies.add(dep);
    return container.read(dep);
  }
}

class MiniProvider<T> {
  final T Function(_DependencyTracker ref) create;
  const MiniProvider(this.create);
}

上面伪代码展示了 Riverpod 的核心:

4. 工作流实现

下面展示在实际项目中的工作流:

lib/
├── api/
│   └── api_service.dart                      # api 相关
|
├── features/
│   ├── air/
│       ├── application/                      # 应用层
│       │   └── providers.dart
│       ├── data/                             # 数据层
│       │   └── history_repository.dart       # 数据仓库
│       ├── domain/                           # 领域层
│       │   ├── history.dart                  # 数据模型
│       │   └── history.g.dart
│       └── presentation/                     # 表现层
│           ├── widget/                       # 通用组件
│           │   └── chart_bar.dart
|           ├── dialog/                       # 对话框组件
│           └── statistics_page.dart          # 统计UI页面
/// lib/api/api.dart
class Api {
  static const String _path = '/dev/v1/';

  // 获取指定时间段的历史数据:/user/nodes/tsdata
  static const String historyData = "${_path}user/nodes/tsdata";
}

/// lib/api/api_service.dart
class ApiService {
  // 获取24小时平均值
  Future<Response> get24hoursData(
      {required String nodeId,
      required String paramName,
      String type = "float"}) async {
    var response = await _httpUtil
        .request(Method.get, Api.historyData, queryParameters: {
      "node_id": nodeId,
      "param_name": "$paramIdentifier.$paramName",
      "type": type,
      "aggregate": "avg",
      "aggregation_interval": "hour",
      "num_intervals": "24"
    });
    return response;
  }
}
/// lib/features/air/data/history_repository.dart
class HistoryRepository {
  final ApiService apiService;

  HistoryRepository({required this.apiService});

  // 获取24小时平均值
  Future<History> get24hoursData(
      {required String nodeId,
      required String paramName,
      String type = "float"}) async {
    var response = await apiService.get24hoursData(
        nodeId: nodeId, paramName: paramName, type: type);
    Log.d("get24hoursData: ${response.data}");
    return History.fromJson(response.data);
  }
}

/// Providers
final historyRepositoryProvider = Provider<HistoryRepository>((ref) {
  return HistoryRepository(apiService: ApiService.instance());
});
import 'package:json_annotation/json_annotation.dart';

part 'history.g.dart';

/// lib/features/air/domain/history.dart
@JsonSerializable()
class History {
  @JsonKey(name: "ts_data")
  final List<TsDatum> tsData;

  History({
    required this.tsData,
  });

  factory History.fromJson(Map<String, dynamic> json) => _$HistoryFromJson(json);

  Map<String, dynamic> toJson() => _$HistoryToJson(this);
}
/// 状态定义
class AirState {
  final AsyncValue<History> data; 

  const AirState({
    this.data = const AsyncLoading(),
  });

  AirState copyWith({AsyncValue<History>? data}) {
    return AirState(data: data ?? this.data);
  }
}

/// 状态管理器
class AirNotifier extends StateNotifier<AirState> {
  final HistoryRepository _repository;
  final String _nodeId;

  AirNotifier(this._repository, this._nodeId) : super(const AirState());

  // 获取历史数据
  Future<History> getHistoryData() async {
    final result =  _repository.get24hoursData(_nodeId);
    if (mounted) {
        state = state.copyWith(data: result);
    }
    return result;
  }
}

// 历史数据状态管理器 provider
final airNotifierProvider = StateNotifierProvider.autoDispose
    .family<AirNotifier, AirState, String>((ref, nodeId) {
  final repository = ref.watch(historyRepositoryProvider);
  return AirNotifier(repository, nodeId);
});
/// lib/features/air/presentation/statistics_page.dart
class StatisticsPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(airNotifierProvider('node_id'));
    return state.when(
      data: (data) => HistoryView(state),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
    );
  }
}

小结

这个工作流展示了完整的数据流向:

  1. API 定义 → 定义网络请求接口
  2. Repository 实现 → 处理数据获取、缓存、错误处理
  3. Domain 模型 → 定义业务数据结构
  4. StateNotifier → 管理复杂业务状态
  5. Provider 定义 → 提供依赖注入和状态访问
  6. UI 消费 → 响应式 UI 更新

数据流向:

UI (Consumer) 
  ↓ ref.watch()
Provider 
  ↓ StateNotifier
Application Layer 
  ↓ Repository
Data Layer 
  ↓ API/Database
External Data Source

5. 注意事项

// ❌ 错误:不必要的长期持有
final expensiveProvider = Provider<ExpensiveService>((ref) {
  return ExpensiveService(); // 会一直存在内存中
});

// ✅ 正确:使用 autoDispose
final expensiveProvider = Provider.autoDispose<ExpensiveService>((ref) {
  final service = ExpensiveService();
  
  // 清理资源
  ref.onDispose(() {
    service.dispose();
  });
  
  return service;
});

// 需要时保持活跃
final cacheProvider = Provider.autoDispose<CacheService>((ref) {
  final cache = CacheService();
  
  // 在有数据时保持活跃
  if (cache.hasData) {
    ref.keepAlive();
  }
  
  return cache;
});
// ❌ 错误:监听整个复杂状态
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userState = ref.watch(userProvider); // 整个状态变化都会重建
    
    return Text(userState.user?.name ?? '');
  }
}

// ✅ 正确:只监听需要的部分
final userNameProvider = Provider<String?>((ref) {
  return ref.watch(userProvider.select((state) => state.user?.name));
});

class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userName = ref.watch(userNameProvider); // 只有名字变化才重建
    
    return Text(userName ?? '');
  }
}

// 或者使用 select
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userName = ref.watch(
      userProvider.select((state) => state.user?.name)
    );
    
    return Text(userName ?? '');
  }
}
// ❌ 错误:Family Provider 参数过于复杂
final userProvider = StateNotifierProvider.family<UserNotifier, UserState, Map<String, dynamic>>((ref, params) {
  return UserNotifier(params['id'], params['config']);
});

// ✅ 正确:使用简单参数或自定义类
class UserParams {
  final String id;
  final UserConfig config;
  
  UserParams(this.id, this.config);
  
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is UserParams &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          config == other.config;

  @override
  int get hashCode => id.hashCode ^ config.hashCode;
}

final userProvider = StateNotifierProvider.family<UserNotifier, UserState, UserParams>((ref, params) {
  return UserNotifier(params.id, params.config);
});
// 使用 select 避免不必要的重建
final isLoadingProvider = Provider<bool>((ref) {
  return ref.watch(userProvider.select((state) => state.isLoading));
});

// 使用 Consumer 局部重建
class UserProfile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 静态内容不会重建
        const Text('用户信息'),
        
        // 只有这部分会根据状态变化重建
        Consumer(
          builder: (context, ref, child) {
            final user = ref.watch(userProvider.select((state) => state.user));
            return Text(user?.name ?? '未登录');
          },
        ),
        
        // 其他静态内容
        const SizedBox(height: 20),
      ],
    );
  }
}

6. 总结

  1. 清晰的分层结构: 每一层都有明确的职责,便于维护和测试
  2. 强类型安全: Riverpod 提供编译时检查,减少运行时错误
  3. 优秀的性能: 精确的重建控制,避免不必要的 UI 更新
  4. 易于测试: 依赖注入和状态隔离使测试变得简单
  5. 可扩展性: 模块化设计便于功能扩展和团队协作
  1. 遵循分层原则: 确保每层只处理自己的职责
  2. 合理使用 Provider: 根据需求选择合适的 Provider 类型
  3. 注意生命周期: 使用 autoDispose 避免内存泄漏
  4. 优化性能: 使用 select 和 Consumer 减少不必要的重建
  5. 统一错误处理: 在合适的层级处理和转换错误
  6. 编写测试: 利用 Riverpod 的测试友好特性编写单元测试

通过合理运用 Riverpod 和分层架构,可以构建出高质量、可维护、可测试的 Flutter 应用。关键是要理解每一层的职责,正确使用 Riverpod 的各种特性,并遵循最佳实践。

上一篇 下一篇

猜你喜欢

热点阅读