这是一个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
,是代表视图
的根Widget
ViewState
,带有另一个类及其控制器
的模板特化。ViewState
包含view
getter,这实际上是UI实现StatefulWidget
按照Flutter
的规定包含State
StatefulWidget
只用于从其他页面传递参数给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
和password
Observer<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); }
一键生成PPT 和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。
openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。
高分辨率纹理 3D 资产生成
Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。
一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。
3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。