Pine

Pine logo

为您的Flutter项目提供轻量级架构助手

Pub Dart CI Star on GitHub License: MIT

如果您想支持此项目,

"Buy Me A Coffee"

入门

Pine依赖以下依赖项

有了这个工具,您将使用Provider轻松定义Flutter应用程序的架构,将元素注入到widget树中,并使用BLoC作为状态管理器。

安装

此包旨在支持Flutter项目的开发。通常,将其放在dependencies下,在您的pubspec.yaml文件中

dev_dependencies:
  pine: ^1.0.0

您可以从命令行安装包

flutter pub get

或者只需通过命令行添加

flutter pub add pine

工作原理

架构

元素从上到下注入。

  1. 添加到widget树的第一个元素是mapper,它在将来自数据层的数据转换为表示层应使用的内容时特别有用。
  2. 第二个元素是provider:在这里,您可以注入操作数据或访问数据的服务,如REST客户端或DAO接口。
  3. 第三层用于注入存储库,这些存储库通过抽象层访问数据层。
  4. 最后一层用于注入逻辑:Pine依赖BLoC作为状态管理器,因此我们将注入全局范围的BLoC。

每个元素可能依赖于顶层元素,并且通常从底层元素访问:例如,存储库可能需要访问REST客户端服务来收集数据,将其保存到数据库,然后将其返回给BLoC。要访问顶层项,您可以使用Provider公开的read和watch函数。

Pine logo

交互

Pine logo

用法

可以通过使用DependencyInjectorHelper widget来实现Pine架构,该widget帮助您将不同类型的元素注入到widget树中。如果您正在处理一个简单的项目,您应该将DependencyInjectorHelper直接放入您的主应用程序widget中。

示例

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => DependencyInjectorHelper(
    blocs: [
      BlocProvider<NewsBloc>(
        create: (context) => NewsBloc(
          newsRepository: context.read(),
        )..fetchNews(),
      ),
    ],
    mappers: [
      Provider<DTOMapper<ArticleDTO, Article>>(
        create: (_) => ArticleMapper(),
      ),
    ],
    providers: [
      Provider<Dio>(
        create: (_) => Dio(),
      ),
      Provider<NewsService>(
        create: (context) => NewsService(
          context.read(),
          baseUrl: 'https://newsapi.org/v2/',
        ),
      ),
    ],
    repositories: [
      RepositoryProvider<NewsRepository>(
        create: (context) => NewsRepositoryImpl(
          newsService: context.read(),
          mapper: context.read(),
        ),
      ),
    ],
    child: MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'News App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    ),
  );
}

随着项目的增长,最好创建一个新的widget来将所有这些项目包装在不同的文件中。我们可以将此widget命名为DependencyInjector。 dependency_injector.dart

part 'blocs.dart';
part 'mappers.dart';
part 'providers.dart';
part 'repositories.dart';

class DependencyInjector extends StatelessWidget {
  final Widget child;

  const DependencyInjector({
    Key? key,
    required this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) => DependencyInjectorHelper(
    blocs: _blocs,
    providers: _providers,
    mappers: _mappers,
    repositories: _repositories,
    child: child,
  );
}

在此widget中,我们需要定义项目中所需的所有依赖项。我倾向于根据类型将这些元素分成不同的文件。在我们的示例中,我们将创建四个不同的文件,因为我们注入了blocs、mappers、providers和repositories。

blocs.dart

part of 'dependency_injector.dart';

final List<BlocProvider> _blocs = [
  BlocProvider<NewsBloc>(
    create: (context) => NewsBloc(
      newsRepository: context.read(),
    )..fetchNews(),
  ),
];

mappers.dart

part of 'dependency_injector.dart';

final List<SingleChildWidget> _mappers = [
  Provider<DTOMapper<ArticleDTO, Article>>(
    create: (_) => ArticleMapper(),
  ),
];

providers.dart

part of 'dependency_injector.dart';

final List<SingleChildWidget> _providers = [
  Provider<Dio>(
    create: (_) => Dio(),
  ),
  Provider<NewsService>(
    create: (context) => NewsService(
      context.read(),
      baseUrl: 'https://newsapi.org/v2/',
    ),
  ),
];

repositories.dart

part of 'dependency_injector.dart';

final List<RepositoryProvider> _repositories = [
  RepositoryProvider<NewsRepository>(
    create: (context) => NewsRepositoryImpl(
      newsService: context.read(),
      mapper: context.read(),
    ),
  ),
];

一旦我们完成了定义要注入到widget树中的全局依赖项,我们就需要如下包装我们的MaterialApp/CupertinoApp与DependencyInjector widget:

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => DependencyInjector(
    child: MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'News App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    ),
  );
}

测试

使用DependencyInjectorHelper,可以轻松地将依赖项注入到widget树中。只需用DependencyInjectorHelper类包装您需要测试的widget,并注入您需要的依赖项。

在下面的示例中,我们将测试 HomePage widget,它依赖于 NewsBloc。在执行包含 HomePage 的 MaterialApp 之前,我们将如下包装它:

    await tester.pumpWidget(
      DependencyInjectorHelper(
        blocs: [
          BlocProvider<NewsBloc>.value(value: newsBloc),
        ],
        child: const MaterialApp(
          home: HomePage(),
        ),
      ),
    );

当然,由于我们正在测试 HomePage,因此我们注入了一个模拟的 newsBloc。

许可证

Pine在MIT许可证下可用。有关更多信息,请参阅LICENSE文件。

附加信息

Pine图标由Freepik – Flaticon创建

GitHub

查看 Github