Flutter Clean Architecture Sample App – Dasher

该项目是 Flutter 应用程序的起点。Dasher App 将向您介绍干净的架构结构以及内层/外层如何连接。

架构结构

Dasher 应用使用 手册 中描述的架构结构。

flutter-architecture-layers

表现层

此层没有业务逻辑,仅用于显示 UI 和处理事件。在 手册 中了解有关表示层的更多信息。

Widgets (UI)

  • 通知 Presenter 事件,例如屏幕显示和用户触摸事件。
  • 观察 Presenter 状态,并在状态更改时重建。

Presenter

  • 包含表示逻辑,通常控制视图状态。

领域层

此层负责业务逻辑。

Interactor

  • Interactor 的主要工作是组合不同的存储库并处理业务逻辑。

Data Holder

  • Singleton 类,将数据保存在内存中,它不调用存储库或其他外层。

Outer layer

Repository

  • 它使用 dio、hive、add2calendar、其他插件等具体实现,并将其从应用程序的其余部分抽象出来。
  • Repository 应该在接口之后。
  • 接口属于域,实现属于外层。

Source remote

  • 表示与远程源(Web、HTTP 客户端、套接字)的通信。

Source local

  • 表示与本地源(数据库、shared_prefs)的通信。

设备

  • 代表与设备硬件(例如传感器)或软件(日历、权限)的通信。

Folder structure

您将在项目中的 /lib 下找到的顶级文件夹结构

img_project_structure

  • app 包含 app run_app,并进行各种设置,例如 flutter.onError 崩溃处理和依赖项初始化。
  • common 包含所有层通用的代码,并且所有层都可以访问。
  • device 是一个外层,代表与设备硬件(例如传感器)或软件(日历、权限)的通信。
  • domain 是内层,通常包含 interactors、data holders。此层应仅包含业务逻辑,并且不应了解 UI、Web 等的特定内容或其他层。
  • source_local 是一个外层,代表与本地源(数据库、shared_prefs)的通信。
  • source_remote 是一个外层,代表与远程源(Web、HTTP 客户端、套接字)的通信。
  • ui 是我们按功能打包小部件和 presenters 的层。Presenters 包含表示逻辑,它们访问 domain,并通过 Provider/Riverpod 包在视图树中提供。
  • main_production.dartmain_staging.dart 两个版本的 main 文件,每个版本都有自己的 flavor。在实践中,这通常意味着有两个版本。在此处 查找有关 flavor 的更多信息

Riverpod and GetIt

此架构结构使用 Riverpod 用于表示层,并使用 GetIt 用于域和外层(source remote、source local 和 device)。

手册 中了解有关如何使用 riverpod 的更多信息。

flutter-architecture-layers-riverpod-getit

架构流程示例

在此示例中,我们将展示在 Dashboard 屏幕上获取新 Tweets 的架构流程。

表现层

Widget

flutter-architecture-layers-riverpod-getit (2)

Dashboard 屏幕上的一个小部件是 DasherTweetsList。在 Tweets 列表小部件中,创建了一个引用来监视 feedRequestPresenter

final _presenter = ref.watch(feedRequestPresenter);

Presenter

flutter-architecture-layers-riverpod-getit (3)

对于 FeedRequestPresenter,我们使用 RequestProvider,您可以在此处 找到有关它的更多信息

FeedRequestPresenter 中,我们创建了一个 FetchFeedInteractor 接口的实例。

final feedRequestPresenter = ChangeNotifierProvider.autoDispose<FeedRequestPresenter>(
  (ref) => FeedRequestPresenter(GetIt.instance.get()),
);

class FeedRequestPresenter extends RequestProvider<List<Tweet>> {
  FeedRequestPresenter(this._feedTimelineInteractor) {
    fetchTweetsTimeline();
  }

  final FetchFeedInteractor _feedTimelineInteractor;

  Future<void> fetchTweetsTimeline() {
    return executeRequest(requestBuilder: _feedTimelineInteractor.fetchFeedTimeline);
  }
}

从这里开始,我们逐渐过渡到 Domain 层。

领域层

Interactor

flutter-architecture-layers-riverpod-getit (4)

Domain 是业务逻辑层,我们有一个 FetchFeedInteractor 的实现,名为 FetchFeedInteractorImpl。我们的任务是创建一个负责处理获取用户时间线 Tweets 的外部逻辑的 Repository 实例。FeedRepository 也位于接口之后。

class FetchFeedInteractorImpl implements FetchFeedInteractor {
  FetchFeedInteractorImpl(this._feedRepository);

  final FeedRepository _feedRepository;

  @override
  Future<List<Tweet>> fetchFeedTimeline() {
    return _feedRepository.fetchFeedTimeline();
  }
}

Repository

flutter-architecture-layers-riverpod-getit (5)

FeedRepositoryImpl 是 Source remote 层的一部分。此存储库使用 twitter_api_v2 包从 Twitter API 获取数据。

Future<List<Tweet>> fetchFeedTimeline() async {
  final response = await twitterApi.tweetsService.lookupHomeTimeline(
    userId: userDataHolder.user!.id,
    tweetFields: [
      TweetField.publicMetrics,
      TweetField.createdAt,
    ],
    userFields: [
      UserField.createdAt,
      UserField.profileImageUrl,
    ],
    expansions: [
      TweetExpansion.authorId,
    ],
  );

  return _getTweetsListWithAuthors(response);
}

在成功响应后,数据会通过他的状态传递回 FeedRequestPresenter,然后触发状态监听器。在 DasherTweetsList 的 build 方法中,我们使用 FeedRequestPresenter 的状态监听器,以便可以轻松地显示/隐藏小部件,具体取决于发出的事件。

flutter-architecture-layers-riverpod-getit (6)

  _presenter.state.maybeWhen(
    success: (feed) => _TweetsList(
      feed: feed,
    ),
    initial: () => const CircularProgressIndicator(),
    loading: (feed) {
      if (feed == null) {
        return const CircularProgressIndicator();
      } else {
        return _TweetsList(
          feed: feed,
        );
      }
    },
    failure: (e) => Text('Error occurred $e'),
    orElse: () => const CircularProgressIndicator(),
  ),

屏幕截图

登录 Feed
个人资料 New Tweet

Infinum 架构 Mason brick

在项目中设置我们架构的最简单方法是使用 Mason bricks。infinum_architecture brick 发布在 https://brickhub.dev/bricks/infinum_architecture/ 上,它将生成所有必需的目录和文件,以便开始项目。

如何使用

Tools to install

确保您已安装 FVM – Flutter Version Management

dart pub global activate fvm

还要安装 Mason CLI,它是使用 Mason bricks 的必备工具。

dart pub global activate mason_cli

Create new project

创建新的 Flutter 项目

flutter create {project_name}

进入项目文件夹

cd {project_name}

Mason brick setup

初始化 mason

mason init

将 mason brick 添加到您的项目

mason add infinum_architecture

开始生成 Infinum 架构文件夹结构

mason make infinum_architecture --on-conflict overwrite

变量

Variable 描述 默认值 类型
project_name 此名称用于命名主函数和文件 run{project_name}App() 示例 string
flutter_version 定义您要安装的 FVM 版本 stable string
brick_look 可选外观 布尔值
brick_request_provider 可选请求提供程序 布尔值

Outputs

? lib
 ┣ ? app
 ┃ ┣ ? di
 ┃ ┃ ┗ ? inject_dependencies.dart
 ┃ ┣ ? example_app.dart
 ┃ ┗ ? run_example_app.dart
 ┣ ? common
 ┃ ┣ ? error_handling
 ┃ ┃ ┣ ? base
 ┃ ┃ ┃ ┣ ? expected_exception.dart
 ┃ ┃ ┃ ┗ ? localized_exception.dart
 ┃ ┃ ┗ ? error_formatter.dart
 ┃ ┣ ? flavor
 ┃ ┃ ┣ ? app_build_mode.dart
 ┃ ┃ ┣ ? flavor.dart
 ┃ ┃ ┣ ? flavor_config.dart
 ┃ ┃ ┗ ? flavor_values.dart
 ┃ ┗ ? logger
 ┃   ┣ ? custom_loggers.dart
 ┃   ┗ ? firebase_log_printer.dart
 ┣ ? device
 ┃ ┗ ? di
 ┃   ┗ ? inject_dependencies.dart
 ┣ ? domain
 ┃ ┗ ? di
 ┃   ┗ ? inject_dependencies.dart
 ┣ ? source_local
 ┃ ┗ ? di
 ┃   ┗ ? inject_dependencies.dart
 ┣ ? source_remote
 ┃ ┗ ? di
 ┃   ┗ ? inject_dependencies.dart
 ┣ ? ui
 ┃ ┣ ? common
 ┃ ┃ ┣ ? generic
 ┃ ┃ ┃ ┗ ? generic_error.dart
 ┃ ┗ ? home
 ┃   ┗ ? home_screen.dart
 ┣ ? main_production.dart
 ┗ ? main_staging.dart

GitHub

查看 Github