Flutter数据清理插件clean_provider的使用
Flutter数据清理插件clean_provider的使用
Clean Provider 💎
一个简单的方式来在Flutter中应用最佳实践,如(关注点分离)。
为什么选择这个包? 🤔
Talat创建此包的原因有很多,包括:
- 社区喜欢Provider 👨💻
- Provider不会试图做所有事情(不强迫你使用特定的库) 👌
- 更好的关注点分离,将逻辑与UI解耦。 💪
Provider
结合ChangeNotifier
可以做到这一点,但不够清晰且不够直接,因为没有前缀模式。它们让你自由发挥,但这可能会导致问题。
这个包取了Provider
的最佳特性,并添加了自己的特色。
索引 📑
安装 ⬇️
在你的pubspec.yaml
文件中添加以下依赖:
dependencies:
clean_provider: ^last
激动人心的部分 🔥
以下是该库的一部分潜力展示。
该库包含以下部分:
View
--> 扩展了StatelessWidget
,提供了更干净的方法来构建UI。Screen
-->View
的响应式版本。ViewModel
--> 业务逻辑或UI级别状态持有者。Page
--> 返回你的View
或Screen
并用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'),
),
);
}
}
开始使用 🚀
为了正确使用它,你需要遵循以下步骤:
- 创建你的
ViewModel
- 创建你的
View
- 创建你的
Page
ViewModel 🧠
- 创建
<your>ViewModel
- 定义你的
Listenable
字段 - 将
Listenable
传递给notifiers
列表以自动更新UI - 定义你的自定义逻辑。
class CounterViewModel extends ViewModel {
int get counter => _counter.value;
final ValueNotifier<int> _counter = 0.listen;
void increment() => _counter.value++;
void decrement() => _counter.value--;
[@override](/user/override)
List<ChangeNotifier> get notifiers =>
<ChangeNotifier>[
_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 => _counter.value;
final ValueNotifier<int> _counter = 0.listen;
[@override](/user/override)
void dispose() {
_counter.dispose();
super.dispose();
}
}
View 📱
一个扩展了StatelessWidget
的类。
- 创建
<your>View
并将<your>ViewModel
作为泛型类型传递给它 - 使用
builder
方法构建你的UI - 使用全局
context
- 使用
viewModel.<your_member>
访问你的<your>ViewModel
成员
class CounterView extends View<CounterViewModel> {
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: <Widget>[
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
接收到的width
或height
。
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
,因为在导航时你会需要它。
- 创建
<your>Page
作为普通的StatelessWidget
- 在
build
方法中返回MultiProvider
- 将你的
<your>View
作为子节点传递 - 将你的提供器传递给
MultiProvider
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<CounterViewModel>(
create: (_) => CounterViewModel(),
)
],
child: CounterView(
title: 'Clean Provider 计数器',
),
);
}
}
推荐文件夹结构 💯
我们强烈建议你使用清洁架构来发挥此包的强大功能。
提示:尝试类似/example
文件夹下的示例。
<future_name> --> 你的(微服务)示例:用户、认证、主页等。
|
|_ data --> 数据层。
| |
| |_ data_sources --> 保存本地或远程的数据源。
| | |
| | |_ remote --> 保存远程数据源的合同及所有实现,即API调用。
| | | |
| | | |_ <future_name>_remote_data_source.dart --> 远程数据源合同。
| | | |_ <future_name>_remote_data_source_impl.dart --> 远程数据源合同的一个实现。
| | | |
| | |_ local --> 保存本地数据源的合同及所有实现,如数据缓存。
| | |
| | |_ <future_name>_local_data_source.dart --> 本地数据源合同。
| | |_ <future_name>_local_data_source_impl.dart --> 本地数据源合同的一个实现。
| |
| |_ models --> 保存转换逻辑。
| | |
| | |_ <entity_name> --> 保存特定实体的转换逻辑。
| | |_ <entity_name>_model.dart
| | |_ <entity_name>_model.g.dart
| |
| |_ <future_name>_repository_impl.dart --> 实现domain层的<future_name>_repository.dart。
|
|
|_ domain --> 独立层,在这一层你应该避免任何逻辑。
| |
| |_ <future_name>_entity.dart --> 实体,这是你的逻辑应该看起来的样子的蓝图。
| |_ <future_name>_repository.dart --> 存储库合同,它是视图模型和数据源之间的中间人。
|
|
|_ providers --> 包含与这个未来相关的所有提供器。
| |_ <future_name>_providers.dart
|
|
|_ presentation --> 表现层。
|
|_ models --> 包含UI模型。
|
|_ view
| |_ <future_name>_scene.dart --> 主要未来UI。
| |_ widgets --> 包含这个未来的所有实用小部件。
|
|_ view_model --> 包含与这个未来相关的所有视图模型。
| |_ <future_name>_view_model.dart --> 主要未来的视图模型。
|
|_ utils --> 包含这个未来中可重用的代码。
示例代码
以下是完整的示例代码:
// 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
更多关于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),
);
}
}