Flutter插件cornerstone的介绍与使用

发布于 1周前 作者 itying888 最后一次编辑是 5天前 来自 Flutter

Flutter插件cornerstone的介绍与使用


Cornerstone

cornerstone codecov pub package

Cornerstone 是一个用纯 Dart 编写的库,旨在帮助开发者在不写大量样板代码的情况下设置符合干净架构的 Dart 项目。它受到干净架构的启发。


目标

Cornerstone 有两个主要目标:

1. 提供可重用的骨架来帮助设置干净架构

第一次学习干净架构并将其应用于实际项目时,我感到对应用程序的可维护性更有信心。重构和功能添加成为愉快的体验。然而,初始设置可能会很痛苦。这就是为什么我创建了 Cornerstone。这是一个库,可以帮助我在一致且少重复的方式下设置干净架构。我希望你也觉得它有帮助。

2. 提供可重用的方式来实现常见用例

大多数现代应用都围绕着通过 API 和数据持久化进行 CRUD 操作。我计划制作一些实用工具功能,以便快速为 Dart 应用程序添加常见功能。

CornerstonePersistentRepositoryMixin

一个混入类,可以轻松地使你的存储库支持本地持久化。我使用了 Hive 来实现这一点。有关更多信息和使用示例,请参阅文件中的 DartDoc 和示例文件夹中的实现示例。


架构

免责声明:即使我会说 Cornerstone 受到干净架构的启发,但我不认为它完全遵循干净架构。你可以在示例文件夹中查看我完成的完整应用以了解更多细节。这是一个用纯 Dart 编写的命令行/终端应用。

Cornerstone Architecture

在本节中,我将专注于解释此库的架构。因此,我建议阅读这篇文章以更全面地了解干净架构: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)、数据库和其他第三方库。数据源通常与平台库交互,例如 HttpClientDioHiveSharedPreferences 等。

这样说来,最好让你的数据源通过平台接口进行交互。例如,如果你需要从 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 上此库的喜欢。如果你想,也可以通过点击下面的按钮请我喝杯咖啡,以激励我将来创建有用的库!感谢你的支持!

Buy Me A Coffee

我也欢迎任何人与我在这个项目上合作。人越多越好!


为什么叫 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

1 回复

更多关于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');
          },
        ),
      ),
    );
  }
}
回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!