这是一个Flutter包,使得在Flutter中实现Uncle Bob的清洁架构变得简单直观。该包提供了根据清洁架构设计并针对Flutter进行调优的基本类。
在你的包的pubspec.yaml文件中添加以下内容:
dependencies: flutter_clean_architecture: ^6.0.1
你可以从命令行安装包:
使用Flutter:
$ flutter packages get
或者,你的编辑器可能支持flutter packages get。查看你的编辑器文档了解更多。
现在在你的Dart代码中,你可以使用:
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';
这是基于Uncle Bob的书籍和博客的架构。它结合了洋葱架构和其他架构的概念。该架构的主要关注点是关注点分离和可扩展性。它由四个主要模块组成:App、Domain、Data和Device。
源代码依赖只指向内部。这意味着内部模块既不知道也不依赖于外部模块。但是,外部模块既知道也依赖于内部模块。外部模块代表了业务规则和策略(内部模块)运作的机制。越往内部移动,抽象程度越高。越往外部移动,具体实现越多。内部模块不知道外部模块中存在的任何类、函数、名称、库等。它们只代表规则,完全独立于实现。
Domain模块定义了应用程序的业务逻辑。它是一个独立于开发平台的模块,即它纯粹用编程语言编写,不包含平台的任何元素。在Flutter的情况下,Domain将纯粹用Dart编写,不包含任何Flutter元素。这是因为Domain应该只关注应用程序的业务逻辑,而不是实现细节。这也允许在平台之间轻松迁移,如果出现任何问题的话。
Domain由几个部分组成。
Login用例期望有login功能的Repository用例Domain代表最内层。因此,它是架构中最抽象的层。
App是Domain外部的层。App跨越层边界与Domain通信。然而,依赖规则从未被违反。使用多态性,App通过继承类与Domain通信:实现或扩展Domain层中存在的Repositories的类。由于使用了多态性,传递给Domain的Repositories仍然遵守依赖规则,因为就Domain而言,它们是抽象的。实现隐藏在多态性之后。
由于App是应用程序的表现层,它是最依赖框架的层,因为它包含UI和UI的事件处理程序。对于应用程序中的每个页面,App至少定义3个类:Controller、Presenter和View。
视图构建页面的UI,设置样式,并依赖控制器处理其事件。视图 包含 控制器。视图由2个类组成
View,是代表视图的根WidgetViewState,带有另一个类及其控制器的模板特化。ViewState包含view getter,这实际上是UI实现StatefulWidget按照Flutter的规定包含StateStatefulWidget只用于从其他页面传递参数给State,如标题等。它只实例化State对象(ViewState)并通过其消费者提供所需的控制器。StatefulWidget 包含 State对象(ViewState),而ViewState 包含 控制器StatefulWidget和State都由页面的View和ViewState表示。ViewState类维护一个GlobalKey,可用作其scaffold的key。如果使用,控制器可以通过getState()轻松访问它以显示snackbar和其他对话框。这很有用但是可选的。ViewState 包含 控制器。控制器提供ViewState所需的成员数据,即动态数据。控制器还实现ViewState小部件的事件处理程序,但无法访问小部件本身。ViewState使用控制器,而不是相反。当ViewState调用控制器的处理程序时,可以调用refreshUI()来更新视图。控制器都继承自Controller抽象类,该类实现了WidgetsBindingObserver。每个控制器类负责处理视图的生命周期事件,可以重写:
控制器 必须 实现 initListeners() 以初始化展示器的监听器,以保持一致性。控制器 包含 展示器。控制器将仓库传递给展示器,后者将与用例通信。控制器将指定展示器应为所有成功和错误事件调用哪些监听器,如前所述。只有控制器被允许从最外层的数据或设备模块获取仓库的实例。控制器可以访问ViewState,并可以通过refreshUI()刷新受控小部件。控制器 包含 展示器。展示器与用例通信,如应用层开始时所述。展示器将有作为函数的成员,这些函数由控制器可选设置,并在用例返回数据、完成或出错时被调用(如果设置了的话)。展示器由两个类组成
展示器 例如 LoginPresenter
控制器设置的事件处理程序用例Observer<T>类和适当的参数初始化并执行用例。例如,在LoginPresenter的情况下使用username和passwordObserver<T>的类
展示器类的引用。理想情况下,这应该是一个内部类,但Dart尚不支持内部类。用例的所有可能输出
用例返回一个对象,它将被传递给onNext(T)。onError(e)。onComplete()。展示器中由控制器设置的相应方法。这样,事件就传递给了控制器,后者可以操作数据并更新ViewState实用工具类(任何常用函数,如时间戳获取器等)常量类(方便使用的const字符串)导航器(如果需要)代表应用程序的数据层。数据模块是最外层的一部分,负责数据检索。这可以是对服务器的API调用、本地数据库,甚至两者都有。
仓库 应该 实现 领域 层的Repository。多态性,这些来自数据层的仓库可以跨层边界传递,从视图开始,通过控制器和展示器一直到用例。实体的扩展,增加了可能依赖平台的额外成员。例如,对于本地数据库,这可以表现为本地数据库中的isDeleted或isDirty条目。这些条目不能出现在实体中,因为那会违反依赖规则,因为领域不应该了解实现细节。数据层的模型将不是必需的,因为我们没有本地数据库。因此,我们不太可能需要在实体中添加依赖平台的额外条目。实体对象映射到模型,反之亦然。实体或模型并返回另一个。模型时才需要实用工具类常量类作为最外层的一部分,设备直接与平台(即Android和iOS)通信。设备负责本机功能,如GPS和平台本身存在的其他功能,如文件系统。设备调用所有本机API。
数据中的仓库,设备是与平台中特定功能通信的类。仓库相同的方式通过层传递:使用应用层和领域层之间的多态性。这意味着控制器将其传递给展示器,然后展示器多态地将其传递给用例,后者将其作为抽象类接收。实用工具类常量类lib/
app/ <--- 应用层
pages/ <-- 页面或屏幕
login/ <-- 应用中的某个页面
login_controller.dart <-- 登录控制器继承自`Controller`
login_presenter.dart <-- 登录展示器继承自`Presenter`
login_view.dart <-- 登录视图,2个类分别继承`View`和`ViewState`
widgets/ <-- 自定义小部件
utils/ <-- 实用函数/类/常量
navigator.dart <-- 可选的应用导航器
data/ <--- 数据层
repositories/ <-- 仓库(检索数据,heavy处理等)
data_auth_repo.dart <-- 示例仓库:处理所有认证
helpers/ <-- 任何辅助类,如http辅助类
constants.dart <-- 常量,如API密钥、路由、URL等
device/ <--- 设备层
repositories/ <--- 与平台通信的仓库,如GPS
utils/ <--- 任何实用类/函数
domain/ <--- 领域层(业务和企业)纯DART
entities/ <--- 企业实体(应用的核心类)
user.dart <-- 示例实体
manager.dart <-- 示例实体
usecases/ <--- 业务流程,如登录、登出、获取用户等
login_usecase.dart <-- 示例用例继承自`UseCase`或`CompletableUseCase`
repositories/ <--- 定义数据和设备层功能的抽象类
main.dart <--- 入口点
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart'; class CounterPage extends View { // 可以在此处注入依赖 State<StatefulWidget> createState() => CounterState(); } class CounterState extends ViewState<CounterPage, CounterController> { CounterState() : super(CounterController()); Widget get view => MaterialApp( title: 'Flutter Demo', home: Scaffold( key: globalKey, // 使 用 `View` 内置的全局键用于 scaffold 或任何其他 // 小部件为控制器提供了通过 getContext()、getState()、getStateKey() 访问它们的方法 body: Column( children: <Widget>[ Center( // 显示按钮被点击的次数 child: ControlledWidgetBuilder<CounterController>( builder: (context, controller) { return Text(controller.counter.toString()); } ), ), // 你可以在控制器内部手动刷新 // 使用 refreshUI() ControlledWidgetBuilder<CounterController>( builder: (context, controller) { return MaterialButton(onPressed: controller.increment); } ), ], ), ), ); }
为了处理 Flutter Web 上的屏幕,你可以利用响应式视图状态,
它抽象了主要的 Web 应用程序断点(桌面、平板和移动),以简化使用 flutter_clean_architecture 进行 Web 开发
例如:
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart'; class CounterPage extends View { // 可以在此处注入依赖 State<StatefulWidget> createState() => CounterState(); } class CounterState extends ResponsiveViewState<CounterPage, CounterController> { CounterState() : super(CounterController()); Widget AppScaffold({Widget child}) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( key: globalKey, // 使用 `View` 内置的全局键用于 scaffold 或任何其他 // 小部件为控制器提供了通过 getContext()、getState()、getStateKey() 访问它们的方法 body: child ), ); } ViewBuilder get mobileView => AppScaffold( child: Column( children: <Widget>[ // 你可以在控制器内部手动刷新 // 使用 refreshUI() ControlledWidgetBuilder<CounterController>( builder: (context, controller) { return Text('移动视图上的计数器 ${controller.counter.toString()}'); } ), ], ) ); ViewBuilder get tabletBuilder => AppScaffold( child: Column( children: <Widget>[ // 你可以在控制器内部手动刷新 // 使用 refreshUI() ControlledWidgetBuilder<CounterController>( builder: (context, controller) { return Text('平 板视图上的计数器 ${controller.counter.toString()}'); } ), ], ) ); ViewBuilder get desktopBuilder => AppScaffold( child: Row( children: <Widget>[ // 你可以在控制器内部手动刷新 // 使用 refreshUI() ControlledWidgetBuilder<CounterController>( builder: (context, controller) { return Text('桌面视图上的计数器 ${controller.counter.toString()}'); } ), ], ) ); }
如果多个小部件需要使用某个 Page 的相同 Controller,
可以通过 FlutterCleanArchitecture.getController<HomeController>(context) 在该页面的子小部件中检索 Controller。
例如:
import '../pages/home/home_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_clean_architecture/flutter_clean_architecture.dart'; class HomePageButton extends StatelessWidget { final String text; HomePageButton({ this.text}); Widget build(BuildContext context) { // 使用通用控制器,假设 HomePageButton 始终是 Home 的子级 HomeController controller = FlutterCleanArchitecture.getController<HomeController>(context); return GestureDetector( onTap: controller.buttonPressed, child: Container( height: 50.0, alignment: FractionalOffset.center, decoration: BoxDecoration( color: Color.fromRGBO(230, 38, 39, 1.0), borderRadius: BorderRadius.circular(25.0), ), child: Text( text, style: const TextStyle( color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.w300, letterSpacing: 0.4), ), ), ); } }
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart'; class CounterController extends Controller { int counter; final LoginPresenter presenter; CounterController() : counter = 0, presenter = LoginPresenter(), super(); void increment() { counter++; } /// 显示一个 snackbar void showSnackBar() { ScaffoldState scaffoldState = getState(); // 获取状态,在这种情况下是 scaffold scaffoldState.showSnackBar(SnackBar(content: Text('你好'))); } void initListeners() { // 在此初始化 presenter 监听器 // 这些将在用例执行后成功、失败或数据检索时被调用 presenter.loginOnComplete = () => print('登录成功'); presenter.loginOnError = (e) => print(e); presenter.loginOnNext = () => print("onNext"); } void login() { // 在此传递适当的凭证 // 假设你有文本字段来检索它们等 presenter.login(); } }
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart'; class LoginPresenter() { Function loginOnComplete; // 或者 `void loginOnComplete();` Function loginOnError; Function loginOnNext; // 在登录 presenter 的情况下不需要 final LoginUseCase loginUseCase; // 从控制器进行依赖注入 LoginPresenter(authenticationRepo): loginUseCase = LoginUseCase(authenticationRepo); /// 控制器调用的登录函数 void login(String email, String password) { loginUseCase.execute(_LoginUseCaseObserver(this), LoginUseCaseParams(email, password)); } /// 处理 [LoginUseCase] 并取消订阅 void dispose() { _loginUseCase.dispose(); } } /// 用于观察 [LoginUseCase] 的 `Stream` 的 [Observer] class _LoginUseCaseObserver implements Observer<void> { // 上面的 presenter // 这不是最优的,但由于 Dart 的限制,这是一个变通方法。Dart 不 // 支持内部类或匿名类。 final LoginPresenter loginPresenter; _LoginUseCaseObserver(this.loginPresenter); /// 如果 `Stream` 发出一个值则实现 // 在这种情况下,不必要 void onNext(_) {} /// 登录成功,在 [LoginController] 中触发事件 void onComplete() { // 任何清理或准备工作都在这里进行 assert(loginPresenter.loginOnComplete != null); loginPresenter.loginOnComplete(); } /// 登录失败,在 [LoginController] 中触发事件 void onError(e) { // 任何清理或准备工作都在这里进行 assert(loginPresenter.loginOnError != null); loginPresenter.loginOnError(e); } }
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart'; // 在这种情况下,不需要参数。因此,void。否则,更改为适当的类型。 class LoginUseCase extends CompletableUseCase<LoginUseCaseParams> { final AuthenticationRepository _authenticationRepository; // 一些需要注入的依赖 // 功能隐藏在这个 // 在 Domain 模块中定义的抽象类后面 // 它应该在 Data 或 Device 模块中实现 // 并以多态方式传递。 LoginUseCase(this._authenticationRepository); // 由于参数类型是 void,`_` 忽略参数。根据模板中使用的类型进行更改。 Future<Stream<void>> buildUseCaseStream(params) async { final StreamController controller = StreamController(); try { // 假设你在这里传递凭证 await _authenticationRepository.authenticate(email: params.email, password: params.password); logger.finest('LoginUseCase 成功。'); // 触发 onComplete controller.close(); } catch (e) { print(e); logger.severe('LoginUseCase 失败。'); // 触发 .onError controller.addError(e); } return controller.stream; } } ```dart class LoginUseCaseParams { final String email; final String password; LoginUseCaseParams(this.email, this.password); }
可以使用 BackgroundUseCase 类在单独的 isolate 上运行用例。
由于 isolate 的限制,实现这种用例与常规用例略有不同。
要创建 BackgroundUseCase,只需扩展该类并重写 buildUseCaseTask 方法。
此方法应返回 UseCaseTask,它只是一个返回类型为 void 且接受 BackgroundUseCaseParameters 参数的函数。
此方法应为静态方法,并将包含您希望在单独的 isolate 上运行的所有代码。此方法应使用 BackgroundUseCaseParameters
中提供的 port 与主 isolate 通信,如下所示。这个例子是执行矩阵乘法的 BackgroundUseCase。
class MatMulUseCase extends BackgroundUseCase<List<List<double>>, MatMulUseCaseParams> { // 必须重写 buildUseCaseTask() { return matmul; // 返回包含要在 isolate 上运行的代码的静态方法 } /// 此方法将在单独的 isolate 上执行。[params] 包含所有数据和所需的 sendPort static void matmul(BackgroundUseCaseParams params) async { MatMulUseCaseParams matMulParams = params.params as MatMulUseCaseParams; List<List<double>> result = List<List<double>>.generate( 10, (i) => List<double>.generate(10, (j) => 0)); for (int i = 0; i < matMulParams.mat1.length; i++) { for (int j = 0; j < matMulParams.mat1.length; j++) { for (int k = 0; k < matMulParams.mat1.length; k++) { result[i][j] += matMulParams.mat1[i][k] * matMulParams.mat2[k][j]; } } } // 将结果发送回主 isolate // 这将转发给观察者监听器 params.port.send(BackgroundUseCaseMessage(data: result)); } }
与常规 [UseCase] 一样,建议为任何 [BackgroundUseCase] 使用参数类。 与上面示例对应的示例如下
class MatMulUseCaseParams { List<List<double>> mat1; List<List<double>> mat2; MatMulUseCaseParams(this.mat1, this.mat2); MatMulUseCaseParams.random() { var size = 10; mat1 = List<List<double>>.generate(size, (i) => List<double>.generate(size, (j) => i.toDouble() * size + j)); mat2 = List<List<double>>.generate(size, (i) => List<double>.generate(size, (j) => i.toDouble() * size + j)); } }
abstract class AuthenticationRepository { Future<void> register( { String firstName, String lastName, String email, String password}); /// 使用 [username] 和 [password] 对用户进行身份验证 Future<void> authenticate( { String email, String password}); /// 返回 [User] 是否已通过身份验证。 Future<bool> isAuthenticated(); /// 返回当前已验证的 [User]。 Future<User> getCurrentUser(); /// 重置 [User] 的密码 Future<void> forgotPassword(String email); /// 注销 [User] Future<void> logout(); }
此仓库应在数据层中实现
class DataAuthenticationRepository extends AuthenticationRepository { // 单例 static DataAuthenticationRepository _instance = DataAuthenticationRepository._internal(); DataAuthenticationRepository._internal(); factory DataAuthenticationRepository() => _instance; Future<void> register( { String firstName, String lastName, String email, String password}) { // 待实现 } /// 使用 [username] 和 [password] 对用户进行身份验证 Future<void> authenticate( { String email, String password}) { // 待实现 } /// 返回 [User] 是否已通过身份验证。 Future<bool> isAuthenticated() { // 待实现 } /// 返回当前已验证的 [User]。 Future<User> getCurrentUser() { // 待实现 } /// 重置 [User] 的密码 Future<void> forgotPassword(String email) { // 待实现 } /// 注销 [User] Future<void> logout() { // 待实现 } }
如果仓库与平台相关,请在设备层中实现。
在领域层中定义。
class User { final String name; final String email; final String uid; User(this.name, this.email, this.uid); }


AI一键生成PPT,就用博思AIPPT!
博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。


AI赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。


企业专属的AI法律顾问
iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。


稳定高效的流量提升解决方案,助力品牌曝光
稳定高效的流量提升解决方案,助力品牌曝光


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频


实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。


选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。


AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


最强AI数据分析助手
小浣熊家族Raccoon,您的AI智能助手,致力于通过先进的人工智能技术,为用户提供高效、便捷的智能服务。无论是日常咨询还是专业问题解答,小浣熊都能以快速、准确的响应满足您的需求,让您的生活更加智能便捷。


像人一样思考的AI智能体
imini 是一款超级AI智能体,能根据人类指令,自主思考、自主完成、并且交付结果的AI智能体。
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号