Flutter 应用程序架构

Flutter 应用程序架构

Riverpod 应用架构参考

使用数据层、域、应用程序和表示层的 Flutter 应用程序架构

此架构由四个不同的层组成,每个层都包含我们的应用所需的组件:

  • presentation: widgets, states, and controllers
  • application: services
  • domain: models
  • data: repositories, data sources, and DTOs (数据传输对象)

按层划分结构

‣ lib
  ‣ src
    ‣ presentation
      ‣ feature1
      ‣ feature2
      ‣ feature3 <--
    ‣ application
      ‣ feature1
      ‣ feature2
      ‣ feature3 <-- only create this when needed
    ‣ domain
      ‣ feature1
      ‣ feature2
      ‣ feature3 <--
    ‣ data
      ‣ feature1
      ‣ feature2
      ‣ feature3 <--

这种方法在实践中很容易使用,但随着应用程序的增长,扩展性不太好。

对于任何给定的功能,属于不同层的文件彼此相距很远。这使得开发各个功能变得更加困难,因为我们必须不断跳转到项目的不同部分。

如果我们决定要删除某个功能,则很容易忘记某些文件,因为它们都是按层组织的。

按功能划分

‣ lib
  ‣ src
    ‣ features
      ‣ feature1
        ‣ presentation
        ‣ application
        ‣ domain
        ‣ data
      ‣ feature2
        ‣ presentation
        ‣ application
        ‣ domain
        ‣ data

我发现这种方法更符合逻辑,因为我们可以轻松查看属于某个功能的所有文件,并按层分组。

优点:

  • 每当我们想要添加一项新功能或修改现有功能时,我们都可以只关注一个文件夹。
  • 如果我们要删除一项功能,则只需删除一个文件夹(如果我们算上相应的tests文件夹,则为两个文件夹)

功能与 UI 无关!

‣ lib
  ‣ src
    ‣ features
      ‣ account
      ‣ admin
      ‣ checkout
      ‣ leave_review_page
      ‣ orders_list
      ‣ product_page
      ‣ products_list
      ‣ shopping_cart
      ‣ sign_in
    ‣ models <-- should this go here?
    ‣ repositories <-- should this go here?
    ‣ services <-- should this go here?

不要尝试通过查看 UI 来应用功能优先方法。这将导致“不平衡”。稍后会困扰您的项目结构。

什么是“features”?
关键不在于用户看到了什么,而在于用户做了什么:

  • 认证
  • 管理购物车
  • 查看
  • 查看所有过去的订单
  • 留下评论

最终确定了七个功能区域:

‣ lib
  ‣ src
    ‣ features
      ‣ address
        ‣ application
        ‣ data
        ‣ domain
        ‣ presentation
      ‣ authentication
        ...
      ‣ cart
        ...
      ‣ checkout
        ...
      ‣ orders
        ...
      ‣ products
        ‣ application
        ‣ data
        ‣ domain
        ‣ presentation
          ‣ admin
          ‣ product_screen
          ‣ products_list
      ‣ reviews
        ...
  • 从领域层开始并确定模型类和用于操作它们的业务逻辑
  • 为属于同一组的每个模型(或模型组)创建一个文件夹
  • 在该文件夹中创建presentation、application、domain、data根据需要设置子文件夹
  • 在每个子文件夹中,添加您需要的所有文件

构建 Flutter 应用时,UI 代码和业务逻辑之间的比例为 5:1(或更高)是很常见的。如果您的 presentation 文件夹最终包含许多文件,请不要害怕将它们分组到代表较小“子功能”的子文件夹中。

最终项目结构:

‣ lib
  ‣ src
    ‣ common_widgets
    ‣ constants
    ‣ exceptions
    ‣ features
      ‣ address
      ‣ authentication
      ‣ cart
      ‣ checkout
      ‣ orders
      ‣ products
      ‣ reviews
    ‣ localization
    ‣ routing
    ‣ utils

应用层

购物车功能使用的层和组件

存储库模式

  • 将域模型(或实体)与数据层中数据源的实现细节隔离。
  • 将数据传输对象转换为领域层可以理解的经过验证的实体
  • 执行数据缓存等操作。

领域模型

领域模型是包含行为和数据的领域概念模型。模型是一个简单的数据类,它无法访问属于域层之外的存储库、服务或其他对象,但是可以包含一些业务逻辑来表达它们的修改方式。为我们的业务逻辑编写单元测试不仅很容易,而且还增加了很多价值

电子商务应用程序:实体及其关系
  • 用户: ID 和电子邮件
  • 产品:ID、图片 URL、标题、价格、可用数量等。
  • 项目:产品 ID 和数量
  • 购物车:商品列表,总计
  • 订单:商品列表、支付价格、状态、付款详细信息等。

表示层

登录功能的分层架构

widget:

  • 观察状态变化,并据此重建(使用 ref.watch)
  • 通过调用控制器中的方法来响应用户输入(使用 ref.read)
  • 监听状态变化并在出现问题时显示错误(使用 ref.listen)

controller:

  • 代表小部件与存储库交互
  • 根据需要发出状态变化

Flutter Riverpod

Flutter Riverpod 是一个Flutter的状态管理库,由Remi Rousselet创建,他也是流行的provider包的作者。Riverpod旨在解决provider包的一些限制,并提供一种更灵活、更可测试的状态管理解决方案。

主要特性

  • 可测试性:Riverpod旨在从一开始就易于测试。你可以独立于Flutter框架对你的状态进行测试,而不需要模拟BuildContext。
  • 解耦:在provider中,状态的位置是由widget树的结构决定的。Riverpod解耦了这一点,你可以将状态的位置与其在widget树中的位置分开。
  • 编译时安全:Riverpod通过提供编译时安全性来避免运行时错误。如果你尝试读取一个未被创建的状态,编译器会警告你。
  • 不可变性:所有的Riverpod状态都是不可变的,这使得状态易于追踪和维护。
  • 灵活性:Riverpod不依赖于BuildContext,这意味着你可以在几乎任何地方访问状态,包括异步代码中。

核心概念

  • Provider:是一个描述如何创建和监听一个值的对象。这个值可以是任何东西:一个变量、一个常量、一个对象等。
  • ConsumerWidget:是一个Widget,它可以监听一个或多个Provider。
  • Ref:在Provider的构建方法中,你将得到一个Ref对象,它可以用来读取其他Provider,或者用于观察Provider的生命周期。
  • ProviderContainer:是存储Provider状态的地方,通常是全局可用的。
  • ProviderObserver:用于观察Provider的变化,这对于调试或日志记录很有用。

如何使用

  1. 定义Provider:首先,你需要创建一个Provider。
final myProvider = Provider((ref) => MyModel());
  1. 读取Provider:然后,你可以在ConsumerWidget中读取你的Provider。
class MyConsumerWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final myModel = ref.watch(myProvider);
    // 使用myModel
    return Text(myModel.someValue);
  }
}
  1. 监听Provider:如果你的Provider发生变化,所有使用了watch的ConsumerWidget都会自动重建。

使用 Flutter Riverpod Generator 自动生成 Providers

"Riverpod Generator" 是一个为Riverpod提供的代码生成工具,旨在简化定义providers的语法,并增强其功能,例如允许状态热重载。这个工具可以自动为你的Riverpod providers生成类型安全的包装器和访问器,减少手动编写和维护这些样板代码的需要。

主要特性

  1. 自动生成代码:Riverpod Generator会为你的应用自动创建必要的provider和访问器代码,你只需定义你的业务逻辑。
  2. 类型安全:生成的代码是类型安全的,减少了运行时错误的可能性。
  3. 易于使用:与其他代码生成工具一样,Riverpod Generator集成到你的开发工作流程中,通常配合build_runner一起使用。
  4. 提高可读性:自动生成的代码遵循一定的模式,有助于保持代码的一致性和可读性。

工作原理

  • 你在你的应用中定义带有特定注解的基本模型和业务逻辑。
  • 使用build_runner运行生成器。
  • 生成器读取你的代码,解析注解,并为你的Riverpod providers生成相应的代码。

如何使用

  1. 添加依赖:你需要在你的pubspec.yaml中添加riverpod_generatorbuild_runner
dependencies:
  # or flutter_riverpod/hooks_riverpod as per https://riverpod.dev/docs/getting_started
  riverpod:
  # the annotation package containing @riverpod
  riverpod_annotation:

dev_dependencies:
  # a tool for running code generators
  build_runner:
  # the code generator
  riverpod_generator:
  # riverpod_lint makes it easier to work with Riverpod
  riverpod_lint:
  # import custom_lint too as riverpod_lint depends on it
  custom_lint:
  1. 启动代码生成器: 使用以下命令启动代码生成器:dart run build_runner watch -d,-d标志是可选的并且与 相同--delete-conflicting-outputs。顾名思义,它确保我们覆盖以前构建的任何冲突输出。
  2. 定义模型和逻辑:在你的代码中定义模型,使用Riverpod Generator提供的注解标记它们。
  3. 使用生成的代码:生成器会为你的Riverpod providers创建 *.g.dart 文件,你可以直接在你的应用中引用和使用这些文件。

示例

假设你有一个简单的模型和需要被监听的状态,你可能会这样定义:

import 'package:dio/dio.dart';
// 1. import the riverpod_annotation package
import 'package:riverpod_annotation/riverpod_annotation.dart';

// 2. add a part file
part 'dio_provider.g.dart';

// 3. use the @riverpod annotation
@riverpod
// 4. update the declaration
Dio dio(DioRef ref) {
  return Dio();
}

生成 dio_provider.g.dart 文件

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'dio_provider.dart';

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$dioHash() => r'26723d20a4ee2d05c3b01acad1196ed96cece567';

/// See also [dio].
@ProviderFor(dio)
final dioProvider = AutoDisposeProvider<Dio>.internal(
  dio,
  name: r'dioProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$dioHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef DioRef = AutoDisposeProviderRef<Dio>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

Flutter Lint & Flutter Snippets

使用

  1. 添加所有需要的包
    pubspec.yaml
dependencies:
  # the main riverpod package for Flutter apps
  flutter_riverpod:
  # the annotation package containing @riverpod
  riverpod_annotation:

dev_dependencies:
  # a tool for running code generators
  build_runner:
  # the code generator
  riverpod_generator:
  # riverpod_lint makes it easier to work with Riverpod
  riverpod_lint:
  # import custom_lint too as riverpod_lint depends on it
  custom_lint:
  1. 启用custom_lint里面的插件analysis_options.yaml:
analyzer:
  plugins:
    - custom_lint
  1. 监视模式下启动 build_runner
dart run build_runner watch -d
  1. 安装插件 Flutter Riverpod Snippets
    https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets

示例

riverpod-part.webp

stateless-ref.webp

Read more

Flutter入门指南

Flutter入门指南

Flutter 是一个由 Google 开发的开源移动应用开发框架。它允许开发者使用一套代码同时构建 iOS 和 Android 应用,并且提供了丰富的 UI 组件和高效的开发工具,使得开发者能够快速构建出高性能的跨平台应用。 一、Flutter 的实现原理 Flutter 的核心在于其自带的高性能渲染引擎 Skia。不同于其他框架依赖于原生的 UI 组件,Flutter 直接通过 Skia 渲染引擎将所有组件绘制到屏幕上。这种方式保证了跨平台应用在 iOS 和 Android 上的表现完全一致。 1.1 结构概览 Flutter 的架构分为三层: 1. Framework(框架层): 这部分主要由 Dart 编写,提供了 Flutter 的各种 UI 组件(Widget)、手势检测、渲染层以及动画等。

By Lewis