Flutter数据清理插件clean_provider的使用

Flutter数据清理插件clean_provider的使用

Clean Provider 💎

一个简单的方式来在Flutter中应用最佳实践,如(关注点分离)。

Github stars Pub Version

为什么选择这个包? 🤔

Talat创建此包的原因有很多,包括:

  1. 社区喜欢Provider 👨‍💻
  2. Provider不会试图做所有事情(不强迫你使用特定的库) 👌
  3. 更好的关注点分离,将逻辑与UI解耦。 💪

Provider结合ChangeNotifier可以做到这一点,但不够清晰且不够直接,因为没有前缀模式。它们让你自由发挥,但这可能会导致问题。

这个包取了Provider的最佳特性,并添加了自己的特色。

索引 📑

安装 ⬇️

在你的pubspec.yaml文件中添加以下依赖:

dependencies:
  clean_provider: ^last

激动人心的部分 🔥

以下是该库的一部分潜力展示。

该库包含以下部分:

  • View --> 扩展了StatelessWidget,提供了更干净的方法来构建UI。
  • Screen --> View的响应式版本。
  • ViewModel --> 业务逻辑或UI级别状态持有者。
  • Page --> 返回你的ViewScreen并用MultiProvider包装的普通StatelessWidget
// 记得设置View的泛型`Type`以便访问自定义属性、getter、setter、方法等。例如,HomeViewModel是ViewModel的子类。
// 如果你不指定View的泛型`Type`,则只能访问预定义的东西,比如context getter。
class HomeView extends View<HomeViewModel> {
  HomeView({super.key});

  // 注意:你可以在widget中全局访问BuildContext。
  [@override](/user/override)
  Widget? builder() {
    return Scaffold(
      appBar: AppBar(
        title: Text(viewModel.title),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: viewModel.navToNext, // 你的自定义方法。
          child: const Text('Go Next'),
        ),
      ),
    );
  }
}

class HomeViewModel extends ViewModel {
  final String title = 'Clean Provider 示例'; // 可以从API获取,不影响你可以发送任何东西到UI。

  void navToNext() {
    Navigator.push(
      context, // 你可以在ViewModel中全局访问BuildContext。
      MaterialPageRoute(
        builder: (context) {
          return const SecondPage();
        },
      ),
    );
  }
}

// 记住我们有侧边栏吗?
// View的类型参数是可选的,所以在这种情况下,你不能访问自定义调用,但仍然可以获取预定义的,比如全局上下文。
class SecondPage extends View {
  SecondPage({super.key});

  [@override](/user/override)
  Widget builder() {
    return const Scaffold(
      body: Center(
        child: Text('Go back'),
      ),
    );
  }
}

开始使用 🚀

为了正确使用它,你需要遵循以下步骤:

  1. 创建你的ViewModel
  2. 创建你的View
  3. 创建你的Page

ViewModel 🧠

  1. 创建<your>ViewModel
  2. 定义你的Listenable字段
  3. Listenable传递给notifiers列表以自动更新UI
  4. 定义你的自定义逻辑。
class CounterViewModel extends ViewModel {
  int get counter =&gt; _counter.value;
  final ValueNotifier&lt;int&gt; _counter = 0.listen;

  void increment() =&gt; _counter.value++;

  void decrement() =&gt; _counter.value--;

  [@override](/user/override)
  List&lt;ChangeNotifier&gt; get notifiers =&gt;
      &lt;ChangeNotifier&gt;[
        _counter,
      ];
}

生命周期 🪴

ViewModel带有一些生命周期方法,例如onInit,它在ViewModel注入后运行。还有著名的onDispose方法,在ViewModel被销毁时需要清理进程。

onInit 🌱

class MyViewModel extends ViewModel {
  [@override](/user/override)
  void onInit() {
    super.onInit();
    // TODO: 添加你的实现。
    // 初始化变量等。
  }
}

onDispose 🥀

class MyViewModel extends ViewModel {
  int get counter =&gt; _counter.value;
  final ValueNotifier&lt;int&gt; _counter = 0.listen;

  [@override](/user/override)
  void dispose() {
    _counter.dispose();
    super.dispose();
  }
}

View 📱

一个扩展了StatelessWidget的类。

  1. 创建<your>View并将<your>ViewModel作为泛型类型传递给它
  2. 使用builder方法构建你的UI
  3. 使用全局context
  4. 使用viewModel.<your_member>访问你的<your>ViewModel成员
class CounterView extends View&lt;CounterViewModel&gt; {
  const CounterView({
    required this.title,
    super.key,
  });

  final String title;

  [@override](/user/override)
  Widget builder() {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme
            .of(context)
            .colorScheme
            .inversePrimary,
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            const Text(
              '你已经按下了按钮多少次:',
            ),
            Text(
              '${viewModel.counter}',
              style: Theme
                  .of(context)
                  .textTheme
                  .headlineMedium,
            ),
            const SizedBox(height: 100),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: viewModel.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }

  [@override](/user/override)
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(StringProperty('title', title));
  }
}

屏幕信息 ℹ️

保存屏幕实用信息,如从MediaQuery接收到的widthheight

class ExampleView extends View {
  ExampleView({super.key});

  [@override](/user/override)
  Widget builder() {
    return Container(
      width: info.width * .5, // 相当于 MediaQuery.of(context).size.width * .5
      height: info.height * .3, // 相当于 MediaQuery.of(context).size.height * .3
    );
  }
}

全局BuildContext 🌐

多亏了ScreenInfo,我们可以全局访问BuildContext(不需要像在StatelessWidget的情况下那样从方法到方法传递context)。

class HomeView extends View {
  // 注意:你可以在widget中全局访问BuildContext。
  [@override](/user/override)
  Widget? builder() {
    return Scaffold(
      appBar: AppBar(
        title: Text(viewModel.title),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context, // 访问BuildContext。
              MaterialPageRoute(
                builder: (context) {
                  return const SecondPage();
                },
              ),
            );
          },
          child: const Text('Go Next'),
        ),
      ),
    );
  }
}

Page ⚙️

你可以叫它任何你喜欢的名字,但我建议你叫它Page,因为在导航时你会需要它。

  1. 创建<your>Page作为普通的StatelessWidget
  2. build方法中返回MultiProvider
  3. 将你的<your>View作为子节点传递
  4. 将你的提供器传递给MultiProvider
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider&lt;CounterViewModel&gt;(
          create: (_) =&gt; CounterViewModel(),
        )
      ],
      child: CounterView(
        title: 'Clean Provider 计数器',
      ),
    );
  }
}

推荐文件夹结构 💯

我们强烈建议你使用清洁架构来发挥此包的强大功能。

提示:尝试类似/example文件夹下的示例。

<future_name> --&gt; 你的(微服务)示例:用户、认证、主页等。
    |
    |_ data --&gt; 数据层。
    |    |
    |    |_ data_sources --&gt; 保存本地或远程的数据源。
    |    |    |
    |    |    |_ remote --&gt; 保存远程数据源的合同及所有实现,即API调用。
    |    |    |    |
    |    |    |    |_ <future_name>_remote_data_source.dart --&gt; 远程数据源合同。
    |    |    |    |_ <future_name>_remote_data_source_impl.dart --&gt; 远程数据源合同的一个实现。
    |    |    |    |
    |    |    |_ local --&gt; 保存本地数据源的合同及所有实现,如数据缓存。
    |    |         |
    |    |         |_ <future_name>_local_data_source.dart --&gt; 本地数据源合同。
    |    |         |_ <future_name>_local_data_source_impl.dart --&gt; 本地数据源合同的一个实现。
    |    |
    |    |_ models --&gt; 保存转换逻辑。
    |    |    |
    |    |    |_ <entity_name> --&gt; 保存特定实体的转换逻辑。
    |    |           |_ <entity_name>_model.dart 
    |    |           |_ <entity_name>_model.g.dart
    |    | 
    |    |_ <future_name>_repository_impl.dart --&gt; 实现domain层的<future_name>_repository.dart。
    |
    |
    |_ domain --&gt; 独立层,在这一层你应该避免任何逻辑。
    |    |
    |    |_ <future_name>_entity.dart --&gt; 实体,这是你的逻辑应该看起来的样子的蓝图。
    |    |_ <future_name>_repository.dart --&gt; 存储库合同,它是视图模型和数据源之间的中间人。
    |
    |
    |_ providers --&gt; 包含与这个未来相关的所有提供器。
    |    |_ <future_name>_providers.dart
    |
    |
    |_ presentation --&gt; 表现层。
         |
         |_ models --&gt; 包含UI模型。
         |
         |_ view
         |    |_ <future_name>_scene.dart --&gt; 主要未来UI。
         |    |_ widgets --&gt; 包含这个未来的所有实用小部件。
         |
         |_ view_model --&gt; 包含与这个未来相关的所有视图模型。
         |     |_ <future_name>_view_model.dart --&gt; 主要未来的视图模型。
         |
         |_ utils --&gt; 包含这个未来中可重用的代码。

示例代码

以下是完整的示例代码:

// Copyright (c) 2024. Talat El Beick. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file.

import 'package:example/src/counter/counter_page.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Clean Provider 计数器应用',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const CounterPage(),
    );
  }
}

更多关于Flutter数据清理插件clean_provider的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据清理插件clean_provider的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


clean_provider 是一个用于 Flutter 应用的数据清理插件,它可以帮助你在应用中更容易地管理和清理数据。这个插件通常与 Provider 状态管理库结合使用,以便在应用的生命周期中自动清理不再需要的数据。

以下是如何使用 clean_provider 插件的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 clean_provider 的依赖:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0
  clean_provider: ^0.1.0

然后运行 flutter pub get 来安装依赖。

2. 创建一个数据模型

你可以创建一个简单的数据模型类,这个类将包含你需要管理和清理的数据。

class MyData {
  String value;

  MyData(this.value);

  void dispose() {
    // 清理资源
    print('Cleaning up MyData');
  }
}

3. 使用 CleanProvider

接下来,你可以使用 CleanProvider 来管理你的数据模型。CleanProvider 会在不再需要时自动调用 dispose 方法。

import 'package:flutter/material.dart';
import 'package:clean_provider/clean_provider.dart';

class MyHomePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('CleanProvider Example'),
      ),
      body: CleanProvider<MyData>(
        create: (_) => MyData('Hello, World!'),
        onDispose: (data) => data.dispose(),
        child: MyChildWidget(),
      ),
    );
  }
}

class MyChildWidget extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    final myData = CleanProvider.of<MyData>(context);

    return Center(
      child: Text(myData.value),
    );
  }
}
回到顶部