Flutter插件cornerstone的介绍与使用
Flutter插件cornerstone的介绍与使用
Cornerstone
Cornerstone 是一个用纯 Dart 编写的库,旨在帮助开发者在不写大量样板代码的情况下设置符合干净架构的 Dart 项目。它受到干净架构的启发。
目标
Cornerstone 有两个主要目标:
1. 提供可重用的骨架来帮助设置干净架构
第一次学习干净架构并将其应用于实际项目时,我感到对应用程序的可维护性更有信心。重构和功能添加成为愉快的体验。然而,初始设置可能会很痛苦。这就是为什么我创建了 Cornerstone。这是一个库,可以帮助我在一致且少重复的方式下设置干净架构。我希望你也觉得它有帮助。
2. 提供可重用的方式来实现常见用例
大多数现代应用都围绕着通过 API 和数据持久化进行 CRUD 操作。我计划制作一些实用工具功能,以便快速为 Dart 应用程序添加常见功能。
CornerstonePersistentRepositoryMixin
一个混入类,可以轻松地使你的存储库支持本地持久化。我使用了 Hive 来实现这一点。有关更多信息和使用示例,请参阅文件中的 DartDoc 和示例文件夹中的实现示例。
架构
免责声明:即使我会说 Cornerstone 受到干净架构的启发,但我不认为它完全遵循干净架构。你可以在示例文件夹中查看我完成的完整应用以了解更多细节。这是一个用纯 Dart 编写的命令行/终端应用。
在本节中,我将专注于解释此库的架构。因此,我建议阅读这篇文章以更全面地了解干净架构:Clean Architecture。
平台(左上角黑色背景部分)
平台是“外部世界”。这包括服务器、数据库、第三方库、Firebase 等。最好使用接口与平台交互。这样可以使层之间更加独立。如果这样做,我还建议你处理平台异常并将它们转换为应用程序的通用异常类型。这使得在存储库中处理这些异常更容易。
在示例项目中我没有使用平台接口,以便简化。
示例平台接口:
abstract class CleanHttpClient {
FutureOr<dynamic> get(String path, {Map<String, dynamic> queryParams});
}
class DioCleanHttpClient extends CleanHttpClient {
final Dio dio;
const DioCleanHttpClient({@required this.dio});
FutureOr<dynamic> get(String path, {Map<String, dynamic> queryParams}) async {
// TODO: 实现 dio 的 get 方法。
}
}
我没有提供它的抽象(至少目前还没有),因为根据需求自行实现它会更容易。
数据源
数据源充当访问“外部世界”的网关,例如服务器(通过 API)、数据库和其他第三方库。数据源通常与平台库交互,例如 HttpClient
、Dio
、Hive
、SharedPreferences
等。
这样说来,最好让你的数据源通过平台接口进行交互。例如,如果你需要从 SharedPreferences
切换到 Hive
作为本地存储解决方案,你可以轻松做到这一点,而不会直接影响你的数据源。
记住,你需要为数据源编写抽象,以便用作存储库的依赖项。
在 Cornerstone 中,有五种类型的抽象数据源可供混合和匹配:
SingleGetterDataSource
MultipleGetterDataSource
CreatorDataSource
UpdaterDataSource
DeleterDataSource
例如,如果你只需要数据源支持 GET 和 CREATE,可以这样编写:
abstract class PeopleDataSource
implements MultipleGetterDataSource<Person, Null>, CreatorDataSource<Person, Person>{}
class PeopleDataSourceImpl extends PeopleDataSource {
final Dio client;
PeopleDataSourceImpl({@required this.client});
@override
FutureOr<List<Person>> readMany({Null param}) async {
// TODO: 实现 readMany
}
@override
FutureOr<Person> create({Person param}) {
// TODO: 实现 create
}
}
有关更多详细信息,请参见文档和示例项目。
存储库
存储库充当多个数据源的枢纽。例如,如果你有多个服务器,存储库是放置获取策略逻辑的地方(更准确地说是数据源调用策略)。
另一件事是异常处理。在你的存储库中,你应该捕获异常,将其转换为失败模型,并将其作为 Left
值返回,使用的是 dartz。这样你就不需要在后续层中总是使用 try-catch 块。
像数据源一样,你也需要先编写抽象,因为用例将通过接口与存储库交互。
关于 LocallyPersistentRepository
有些人可能更喜欢将本地持久化策略放在数据源中。这是可以的,因为在我的旧项目中,我也这样做过。但是,在进一步考虑后,我决定将 Cornerstone 的本地持久化策略抽象类命名为“存储库”而不是“数据源”。因为对我来说,持久化数据可以被认为是“存储库的事情”。此外,我认为这种方法会使代码更容易阅读,因为它会产生更清晰的关注分离,例如:
- 数据源:检索数据
- 存储库:管理检索到的数据
在我的示例项目中,我将 LocallyPersistentRepository
实现为混入类。这样,你可以重用本地持久化逻辑,或者如果不希望为存储库实现持久化,也可以完全不使用它。
用例
用例是应用程序业务逻辑和验证所在的地方。这一层应该独立于数据库或展示逻辑。其理念是,只有当业务流程发生变化时,用例才会改变。
用例可以依赖/调用另一个用例。这样,保持每个用例尽可能单一关注会更好。
将它们组合在一起
有几种方法可以将这些层组合在一起。首先,可以使用类似 InheritedWidget
的东西。但对我来说,我更喜欢使用服务定位器库,名为 Get It。
这是我在我示例项目中所做的方式:
void initArchitecture() {
EquatableConfig.stringify = true;
Hive.init(Directory.current.path + '/db');
GetIt.I.registerLazySingleton(() => Dio());
GetIt.I.registerLazySingleton<PeopleDataSource>(
() => PeopleDataSourceImpl(client: GetIt.I()),
);
GetIt.I.registerLazySingleton<PeopleRepository>(
() => PeopleRepositoryImpl(dataSource: GetIt.I(), hive: Hive),
);
GetIt.I.registerLazySingleton(() => GetPeople(repo: GetIt.I()));
GetIt.I.registerLazySingleton(() => LoadPeople(repo: GetIt.I()));
GetIt.I.registerLazySingleton(() => ClearPeopleStorage(repo: GetIt.I()));
}
然后,只需在主函数中调用它:
void main() async {
initArchitecture();
/// 其余代码
}
在较大的项目中,建议将其拆分为几个初始化文件以提高可读性,例如:
/// 在 lib/platforms 下
void initPlatforms() {
EquatableConfig.stringify = true;
Hive.init(Directory.current.path + '/db');
GetIt.I.registerLazySingleton(() => Dio());
}
/// 在 lib/data_sources 下
void initDataSources() {
GetIt.I.registerLazySingleton<PeopleDataSource>(
() => PeopleDataSourceImpl(client: GetIt.I()),
);
}
/// 在 lib/repositories 下
void initRepositories() {
GetIt.I.registerLazySingleton<PeopleRepository>(
() => PeopleRepositoryImpl(dataSource: GetIt.I(), hive: Hive),
);
}
/// 在 lib/use_cases 下
void initUseCases() {
GetIt.I.registerLazySingleton(() => GetPeople(repo: GetIt.I()));
GetIt.I.registerLazySingleton(() => LoadPeople(repo: GetIt.I()));
GetIt.I.registerLazySingleton(() => ClearPeopleStorage(repo: GetIt.I()));
}
/// 在 lib 下
void initArchitecture() {
initPlatforms();
initDataSources();
initRepositories();
initUseCases();
}
接下来是什么?
- 流抽象
- 更多常见用例的实现
支持
我很感激任何对 GitHub 仓库的点赞以及对 pub.dev 上此库的喜欢。如果你想,也可以通过点击下面的按钮请我喝杯咖啡,以激励我将来创建有用的库!感谢你的支持!
我也欢迎任何人与我在这个项目上合作。人越多越好!
为什么叫 Cornerstone?
“基石(或基础石或定位石)是建筑中第一块被放置的石头。所有其他石头都将以此为基础进行定位,从而确定整个结构的位置。”(Wikipedia)
完整示例代码
以下是完整的示例代码,展示了如何使用 Cornerstone:
import 'dart:convert';
import 'dart:io';
import 'package:cornerstone/cornerstone.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
import 'package:example/data_sources/people_data_source.dart';
import 'package:example/repositories/people_repository.dart';
import 'package:example/use_cases/clear_people_storage.dart';
import 'package:example/use_cases/get_people.dart';
import 'package:example/use_cases/load_people.dart';
import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
void main() async {
initArchitecture();
DateFormat formatter = DateFormat('EEE, dd MMM yyyy (HH:mm:ss)', 'en_US');
String selectedOption;
do {
print(
'==================================================\n'
'## Select one option and press enter ##\n\n'
'0. Exit\n'
'1. Get people data from server\n'
'2. Load locally-stored data\n'
'3. Clear locally-stored data\n\n'
'Enter your choice = ',
);
selectedOption = stdin.readLineSync(encoding: Encoding.getByName('utf-8'));
if (selectedOption == '0') {
print('Bye bye!\n');
} else if (selectedOption == '1' || selectedOption == '2') {
print('Please wait...\n');
Either<Failure, PeopleSnapshot> result;
switch (selectedOption) {
case '1':
result = await GetIt.I<GetPeople>()();
break;
case '2':
result = await GetIt.I<LoadPeople>()();
break;
}
result.fold(
(f) => print('[FAILED]\n${f.toString()}\n'),
(d) {
print(
'[SUCCESS]\n'
'Data Fetched At = ${d.updatedAt != null ? formatter.format(d.updatedAt) : 'N/A'}\n'
'Stored Locally? = ${d.isSaved}\n'
'Data = ${d.data}\n\n'
'Current Time = ${formatter.format(DateTime.now())}\n',
);
},
);
} else if (selectedOption == '3') {
final result = await GetIt.I<ClearPeopleStorage>()();
result.fold(
(f) => print('[FAILED]\n${f.toString()}\n'),
(d) => print('[SUCCESS]\n${d.toString()}'),
);
} else {
print('Unexpected option. Please see the hint.\n');
}
} while (selectedOption != '0');
print('==================================================\n');
}
void initArchitecture() {
EquatableConfig.stringify = true;
Hive.init(Directory.current.path + '/db');
GetIt.I.registerLazySingleton(() => Dio());
GetIt.I.registerLazySingleton<PeopleDataSource>(
() => PeopleDataSourceImpl(client: GetIt.I()),
);
GetIt.I.registerLazySingleton<PeopleRepository>(
() => PeopleRepositoryImpl(dataSource: GetIt.I(), hive: Hive),
);
GetIt.I.registerLazySingleton(() => GetPeople(repo: GetIt.I()));
GetIt.I.registerLazySingleton(() => LoadPeople(repo: GetIt.I()));
GetIt.I.registerLazySingleton(() => ClearPeopleStorage(repo: GetIt.I()));
}
更多关于Flutter插件cornerstone的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter插件cornerstone的介绍与使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
由于 cornerstone
插件的介绍为 undefined
,我们无法准确了解其功能和用途。不过,基于插件名称的推测,cornerstone
可能是一个用于处理图像、视频、音频等多媒体内容的插件,或者是一个用于布局、UI 组件的基础工具。以下是一些可能的使用场景和示例代码,供你参考。
1. 图像处理
假设 cornerstone
是一个用于图像处理的插件,可能包含图像加载、裁剪、滤镜等功能。
import 'package:flutter/material.dart';
import 'package:cornerstone/cornerstone.dart'; // 假设的导入
class ImageProcessingPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Processing'),
),
body: Center(
child: CornerstoneImage(
imageUrl: 'https://example.com/your-image.jpg',
fit: BoxFit.cover,
onImageLoaded: (image) {
print('Image loaded successfully');
},
onError: (error) {
print('Failed to load image: $error');
},
),
),
);
}
}
2. 视频播放
如果 cornerstone
是一个视频播放插件,可能提供视频播放、控制、全屏等功能。
import 'package:flutter/material.dart';
import 'package:cornerstone/cornerstone.dart'; // 假设的导入
class VideoPlayerPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Video Player'),
),
body: Center(
child: CornerstoneVideoPlayer(
videoUrl: 'https://example.com/your-video.mp4',
autoPlay: true,
looping: false,
onVideoEnded: () {
print('Video playback ended');
},
),
),
);
}
}
3. 音频播放
如果 cornerstone
是一个音频播放插件,可能提供音频播放、控制、进度条等功能。
import 'package:flutter/material.dart';
import 'package:cornerstone/cornerstone.dart'; // 假设的导入
class AudioPlayerPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Audio Player'),
),
body: Center(
child: CornerstoneAudioPlayer(
audioUrl: 'https://example.com/your-audio.mp3',
autoPlay: true,
onAudioEnded: () {
print('Audio playback ended');
},
),
),
);
}
}
4. UI 组件
如果 cornerstone
是一个 UI 组件库,可能提供一些常用的布局、按钮、卡片等组件。
import 'package:flutter/material.dart';
import 'package:cornerstone/cornerstone.dart'; // 假设的导入
class UIComponentsPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('UI Components'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CornerstoneButton(
onPressed: () {
print('Button pressed');
},
child: Text('Click Me'),
),
SizedBox(height: 20),
CornerstoneCard(
child: Text('This is a card'),
),
],
),
),
);
}
}
5. 数据存储
如果 cornerstone
是一个数据存储插件,可能提供本地存储、缓存、数据库等功能。
import 'package:flutter/material.dart';
import 'package:cornerstone/cornerstone.dart'; // 假设的导入
class DataStoragePage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Data Storage'),
),
body: Center(
child: CornerstoneDataStorage(
key: 'myKey',
value: 'myValue',
onSaved: () {
print('Data saved successfully');
},
onError: (error) {
print('Failed to save data: $error');
},
),
),
);
}
}