# flutter_provider_MVVM **Repository Path**: docname/flutter_provider_-mvvm ## Basic Information - **Project Name**: flutter_provider_MVVM - **Description**: flutter状态管理工具provider,使用MVVM架构进行开发 - **Primary Language**: Dart - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-11-16 - **Last Updated**: 2023-11-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### flutter_provider_MVVM flutter状态管理工具provider,使用MVVM架构进行开发 MVVM架构分为M(Model)、V(View)、VM(ViewModel)三个部分,他们分别处理自己的分工,在View和Model之间使用ViewModel作为中介者,使View和Model不受业务逻辑影响。 Model: 模型层,处理Api数据、模型相关业务 View: 视图层,UI呈现、使用者互动等。 ViewModel: 视图模型,处理逻辑、将数据绑定给View展示。 MVVM运行原理 当界面需要展示数据时,View跟ViewModel绑定,ViewModel向Model取得数据后,在ViewModel处理对应的业务逻辑,然后将数据处理,最后通过View更新并展示。 MVVM优点 易于变更需求,降低耦合 权责分工明确 方便测试 MVVM缺点 文件数量增加 bug定位较为不易 数据绑定消耗资源 MVVM实战 下面这个项目实战是用Provider和MVVM搭建的架构,是一个笑话段子列表。 它包含了5主要类: Service: 网络请求类 Model: 主要负责转换模型 View: 主要负责呈现UI,通过ViewModel获取数据并展示 Widgets: 单独的UI模块分离 ViewModel: 处理业务逻辑,将数据绑定给View展示 定义模型 将网络请求回来的数据转换为对应的模型 [数据模型json to Dart ](https://javiercbk.github.io/json_to_dart/) ``` import 'dart:convert'; JokeModel jokeModelFromJson(String str) => JokeModel.fromJson(json.decode(str)); String jokeModelToJson(JokeModel data) => json.encode(data.toJson()); class JokeModel { JokeModel({ this.data, }); final List? data; factory JokeModel.fromJson(Map json) => JokeModel( data: List.from(json["data"].map((x) => Joke.fromJson(x))), ); Map toJson() => { "data": List.from(data!.map((x) => x.toJson())), }; } class Joke { Joke({ this.content, this.hashId, this.unixtime, this.updatetime, }); final String? content; final String? hashId; final int? unixtime; final String? updatetime; factory Joke.fromJson(Map json) => Joke( content: json["content"], hashId: json["hashId"], unixtime: json["unixtime"], updatetime: json["updatetime"], ); Map toJson() => { "content": content, "hashId": hashId, "unixtime": unixtime, "updatetime": updatetime, }; } ``` 定义网络请求类 网络请求用到第三方网路请求库Dio ^4.0.0,将请求回来的数据转换为模型,并更新ViewModel数据。 ``` import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter_provider_example/provider_mvvm_example/model/joke_model.dart'; import 'package:flutter_provider_example/provider_mvvm_example/view_model/joke_view_model.dart'; class JokeService { static Future getJokes(JokeViewModel jokeViewModel) async { var response = await Dio().get("http://v.juhe.cn/joke/content/text.php?page=1&pagesize=20&key=03303e4d34effe095cf6a4257474cda9"); if (response.statusCode == 200) { // 转换模型 JokeModel jokeModel = jokeModelFromJson(json.encode(response.data["result"])); // 更新数据 jokeViewModel.setJokeList(jokeModel); } } } ``` 定义ViewModel 这个ViewModel主要负责把请求回来的数据进行处理,并通知View层更新数据 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_mvvm_example/model/joke_model.dart'; class JokeViewModel with ChangeNotifier { List? _jokeList = []; late Joke _joke; bool loading = true; setJokeList(JokeModel jokeModel) { _jokeList = []; _jokeList = jokeModel.data; loading = false; notifyListeners(); } setJoke(Joke joke) { _joke = joke; } List? get jokeList => _jokeList; Joke get joke => _joke; } ``` 定义View 我们在页面刚进入时进行初始化,然后通过Provider的Consumer来进行监听状态的变化。 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_mvvm_example/service/joke_service.dart'; import 'package:flutter_provider_example/provider_mvvm_example/view_model/joke_view_model.dart'; import 'package:flutter_provider_example/provider_mvvm_example/widgets/joke_item.dart'; import 'package:provider/provider.dart'; class JokeView extends StatefulWidget { @override _JokeViewState createState() => _JokeViewState(); } class _JokeViewState extends State { @override void initState() { // 获取接口数据 JokeService.getJokes(Provider.of(context, listen: false)); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Provider + MVVM"), ), body: Consumer( builder: (_, jokeViewModel, child) { JokeViewModel _jokeViewModel = jokeViewModel; if (jokeViewModel.loading) { return Center( child: CircularProgressIndicator(), ); } return ListView.separated( itemBuilder: (_, index) { _jokeViewModel.setJoke(_jokeViewModel.jokeList![index]); return JokeItem(jokeViewModel: _jokeViewModel); }, itemCount: _jokeViewModel.jokeList?.length ?? 0, separatorBuilder: (_, index) { return Divider( height: 1, ); }, ); }, ), ); } } ``` 定义Widgets 把需要单独抽离的UI放在widgets中,并把ViewModel传入进来。 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_mvvm_example/model/joke_model.dart'; import 'package:flutter_provider_example/provider_mvvm_example/view_model/joke_view_model.dart'; class JokeItem extends StatelessWidget { JokeItem({ Key? key, this.jokeViewModel }) : super(key: key); final JokeViewModel? jokeViewModel; @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only( left: 15, right: 15, top: 10, bottom: 10 ), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text("${jokeViewModel?.joke?.content ?? ""}", style: TextStyle( color: Colors.black87, letterSpacing: 1.3, wordSpacing: 2 ), ), SizedBox(height: 5,), Text("${jokeViewModel?.joke?.updatetime ?? "--"}") ], ), ); } } ``` 引用View ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_mvvm_example/view/joke_view.dart'; class ProviderMvvmExample extends StatelessWidget { @override Widget build(BuildContext context) { return JokeView(); } } ``` 应用程序入口设置 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_mvvm_example/provider_mvvm_example.dart'; import 'package:flutter_provider_example/provider_mvvm_example/view_model/joke_view_model.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => JokeViewModel(), child: MaterialApp( debugShowCheckedModeBanner: false, home: ProviderMvvmExample(), ), ); } } ``` #### Provider介绍和使用 Provider是一个由社区构建的状态管理包,而不是Google推出,但Provider是Google极力推荐的状态管理方式之一,它是对InheritedWidget组件进行了封装,使其更易用,更易复用。 学习本章节前,希望你能了解如下知识: 熟悉dart语言 熟悉flutter基本组件 了解InheritedWidget 了解ChangeNotifier 如果大家大家之前没接触过InheritedWidget,那么建议你先去了解,你可以通过链接来查看并掌握 Provider优势 我们为什么要用Provider而不是直接使用InheritedWidget,我们看下官方介绍 简化的资源分配与处置 懒加载 创建新类时减少大量的模板代码 支持 DevTools 更通用的调用 InheritedWidget 的方式(参考 Provider.of/Consumer/Selector) 提升类的可扩展性,整体的监听架构时间复杂度以指数级增长(如 ChangeNotifier, 其复杂度为 O(N)) Provider类结构图 ![输入图片说明](https://gz-ljm-blog.oss-cn-guangzhou.aliyuncs.com/blog/flutter_provider_class.png) Provider类说明 ``` Nested组件 Nested: 简化树结构嵌套过深 SingleChildWidget: 单个子组件的组件,但是它与ProxyWidget不同,有一个build方法。 SingleChildStatelessWidget: 它是一个实现SingleChildWidget的StatelessWidget,必须使用buildWithChild构建子组件 SingleChildStatefulWidget: 它是一个实现SingleChildWidget的StatefulWidget,是与Nested兼容的StatefulWidget Provider组件 Provider组件分为四大类,分别如下: Nested系列 MultiProvider: 主要作用是提高代码可读性和减少重复代码,是将多个提供者合并成单个线性的组件提供者。 SingleChildStatefulWidget系列 Selector0: 它是Selector至Selector6的基类 Selector1-6: 它们是将Selector0与Provider.of结合使用的语法糖,继承自Selector0 SingleChildStatelessWidget系列 Consumer1-6: 消费者,只是调用了Provider.of,主要作用是从顶层获取Provider并将其值传递给了builder。 InheritedProvider: InheritedWidget的通用实现,并且所有的继承自该类的都可以通过Provider.of来获取value DeferredInheritedProvider: InheritedProvider的子类,用于监听流或者接收一个Future StreamProvider: 监听流,并暴露出当前的最新值。 FutureProvider: 接收一个 Future,并在其进入 complete 状态时更新依赖它的组件。 ListenableProvider: 供可监听对象使用的特殊 provider,ListenableProvider 会监听对象,并在监听器被调用时更新依赖此对象的widgets。 ChangeNotifierProvider: 为 ChangeNotifier 提供的 ListenableProvider规范,会在需要时自动调用 ChangeNotifier.dispose。 ListenableProxyProvider0: 可见的代理提供者,主要是从其他提供者获取值。 ListenableProxyProvider1-6: 它是ListenableProvider的一个变体,继承ListenableProxyProvider0, 从其他提供者获取值 ChangeNotifierProxyProvider0: 主要用于构建和同步ChangeNotifier的ChangeNotifierProvider。 Provider: 最基础的 provider 组成,接收一个任意值并暴露它。 ProxyProvider0: 它公开的值会通过创建或更新构建,然后传递给 InheritedProvider。 ProxyProvider1-6: ProxyProvider0的语法糖。 InheritedContext系列 InheritedContext: 与InheritedProvider关联的BuildContext,提供公开的值 ReadContext: 公开读取方法 SelectContext: 在 BuildContext 上添加一个选择方法。 WatchContext: 公开 watch 方法。 ``` #### Provider基本使用 第一步:添加依赖 本文中所有的代码都是基本空安全的,所有dart sdk版本为>=2.12.0 <3.0.0,目前官方最新版本为^6.0.1 ``` environment: sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter provider: ^6.0.1 ``` 第二步:定义需要共享的数据 我们这里创建了一个类CountNotifier继承了ChangeNotifier,我们这里的案例是以计数器为案例,所有我们定义一个变量count,以及一个改变数值的increment方法,当我们调用increment后会对count进行+1,最后调用notifyListeners()来更新数据,代码如下: ``` import 'package:flutter/material.dart'; class CountNotifier with ChangeNotifier { int count = 0; void increment() { count++; notifyListeners(); } } ``` 第三步:在应用程序入口初始化 我们在MaterialApp之前对定义的共享数据进行了初始化,代码如下: ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_count_example/count_notifier.dart'; import 'package:flutter_provider_example/provider_count_example/provider_count_example.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => CountNotifier(), child: MaterialApp( debugShowCheckedModeBanner: false, home: ProviderCountExample(), ), ); } } ``` 第四步:使用共享数据 我们定义了一个counter变量来获取到共享的数据,更新数据的方法直接通过counter.increment(),获取数据的方法通过ounter.count.toString()来获取,代码如下: ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_count_example/count_notifier.dart'; import 'package:provider/provider.dart'; class ProviderCountExample extends StatefulWidget { @override _ProviderCountExampleState createState() => _ProviderCountExampleState(); } class _ProviderCountExampleState extends State { @override Widget build(BuildContext context) { final counter = Provider.of(context); return Scaffold( appBar: AppBar( title: Text("InheritedWidget"), ), floatingActionButton: FloatingActionButton( onPressed: (){ counter.increment(); }, child: Icon(Icons.add), ), body: Center( child: Text(counter.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), ), ); } } ``` 总结 以上是对Provider进行了介绍、优势、类结构和说明以及一个基本使用的例子,相对于使用InheritedWidget来说,显然Provider使用起来更简单。但是从它的提供者、消费者这些类来看稍微复杂,后面的章节中我们来讲讲这些类 #### Flutter Provider状态管理---八种提供者使用分析 前言 在我们上一篇文章中对Provider进行了介绍以及类结构的说明,最后还写了一个简单的示例,通过上一章节我们对Provider有了一个基本的了解,这一章节我们来说说Provider的8种提供者以及他们的使用区别。 Provider Provider是最基本的Provider组件,可以使用它为组件树中的任何位置提供值,但是当该值更改的时候,它并不会更新UI,下面我们给出一个示例 第一步:创建模型 ``` class UserModel { String name = "Jimi"; void changeName() { name = "hello"; } } ``` 第二步:应用程序入口设置 ``` return Provider( create: (_) => UserModel(), child: MaterialApp( debugShowCheckedModeBanner: false, home: ProviderExample(), ), ); ``` 第三步:使用共享数据 关于Consumer后面将消费者在提及,我们这里只需要知道有两个消费者,第一个用于展示模型的数据,第二个用于改变模型的数据。 第一个Comsumer是用于读取模型的数据name 第二个Consumer用于改变模型的数据name ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/provider_example/user_model.dart'; import 'package:provider/provider.dart'; class ProviderExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ProviderExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (_, userModel, child) { return Text(userModel.name, style: TextStyle( color: Colors.red, fontSize: 30 ) ); }, ), Consumer( builder: (_, userModel, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ userModel.changeName(); }, child: Text("改变值"), ), ); }, ), ], ), ), ); } } ``` 运行结果 我们点击按钮的会导致模型数据改变,但是模型数据改变之后UI并没有变化也没有重建,那是因为Provider提供者组件不会监听它提供的值的变化。 ##### ChangeNotifierProvider 它跟Provider组件不同,ChangeNotifierProvider会监听模型对象的变化,而且当数据改变时,它也会重建Consumer(消费者),下面我们给出一个示例 第一步:创建模型 细心点我们可以发现这里定义的模型有两处变化,如下: 混入了ChangeNotifier 调用了notifyListeners() 因为模型类使用了ChangeNotifier,那么我们就可以访问notifyListeners()并且在调用它的任何时候,ChangeNotifierProvider都会收到通知并且消费者将重建UI。 ``` import 'package:flutter/material.dart'; class UserModel1 with ChangeNotifier { String name = "Jimi"; void changeName() { name = "hello"; notifyListeners(); } } ``` 第二步:应用程序入口设置 ``` return ChangeNotifierProvider( create: (_) => UserModel1(), child: MaterialApp( debugShowCheckedModeBanner: false, home: ChangeNotifierProviderExample(), ), ); ``` 第三步:使用共享数据 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_provider_example/user_model1.dart'; import 'package:provider/provider.dart'; class ChangeNotifierProviderExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ChangeNotifierProvider"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (_, userModel, child) { return Text(userModel.name, style: TextStyle( color: Colors.red, fontSize: 30 ) ); }, ), Consumer( builder: (_, userModel, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ userModel.changeName(); }, child: Text("改变值"), ), ); }, ), ], ), ), ); } } ``` 运行结果 ##### FutureProvider 简单来说,FutureProvider用于提供在组件树中准备好使用其值时可能尚未准备好的值,主要是确保空值不会传递给任何子组件,而且FutureProvider有一个初始值,子组件可以使用该Future值并告诉子组件使用新的值来进行重建。 注意: FutureProvider只会重建一次 默认显示初始值 然后显示Future值 最后不会再次重建 第一步:创建模型 这里和Provider不同的是增加了构造函数,以及changeName变成了Future,我们模拟网络请求延迟两秒后改变其值。 ``` class UserModel2{ UserModel2({this.name}); String? name = "Jimi"; Future changeName() async { await Future.delayed(Duration(milliseconds: 2000)); name = "hello"; } } ``` 第二步:提供Future 我们有一个方法,就是异步获取userModel2,模拟网络请求延迟两秒执行,最后修改了name并返回UserModel2 ``` import 'package:flutter_provider_example/future_provider_example/user_model2.dart'; class UserFuture { Future asyncGetUserModel2() async { await Future.delayed(Duration(milliseconds: 2000)); return UserModel2(name: "获取新的数据"); } } ``` 第三步:应用程序入口设置 initialData是默认值,create参数我们传了一个Future,因为它接收的模型Create?> ``` return FutureProvider( initialData: UserModel2(name: "hello"), create: (_) => UserFuture().asyncGetUserModel2(), child: MaterialApp( debugShowCheckedModeBanner: false, home: FutureProviderExample(), ), ); ``` 第四步:使用共享数据 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/future_provider_example/user_model2.dart'; import 'package:provider/provider.dart'; class FutureProviderExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("FutureProviderExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (_, userModel, child) { return Text(userModel.name ?? "", style: TextStyle( color: Colors.red, fontSize: 30 ) ); }, ), Consumer( builder: (_, userModel, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ userModel.changeName(); }, child: Text("改变值"), ), ); }, ), ], ), ), ); } } ``` 运行结果 我们可以看到先展示默认值hello,最后获取到结果的时候展示了获取新的数据,我们尝试改变其值,虽然值改变但是并没有刷新UI。 ##### StreamProvider StreamProvider提供流值,是围绕StreamBuilder,所提供的值会在传入的时候替换掉新值。和FutureProvider一样,主要的区别在于值会根据多次触发重新构建UI。 如果你对StreamBuilder不太了解的话,那么你就很难理解StreamProvider,StreamProvider文档地址 第一步:创建模型 ``` class UserModel3{ UserModel3({this.name}); String? name = "Jimi"; void changeName() { name = "hello"; } } ``` 第二步:提供Stream 下面这段代码类似计时器,每隔一秒钟生成一个数字 ``` import 'package:flutter_provider_example/stream_provider_example/user_model3.dart'; class UserStream { Stream getStreamUserModel() { return Stream.periodic(Duration(milliseconds: 1000), (value) => UserModel3(name: "$value") ).take(10); } } ``` 第三步:应用程序入口设置 这里也有initialData初始值,和FutureProvider类似,只是create属性是获取一个Stream流。 ``` return StreamProvider( initialData: UserModel3(name: "hello"), create: (_) => UserStream().getStreamUserModel(), child: MaterialApp( debugShowCheckedModeBanner: false, home: StreamProviderExample(), ), ); ``` 第四步:使用共享数据 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/stream_provider_example/user_model3.dart'; import 'package:provider/provider.dart'; class StreamProviderExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("StreamProviderExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (_, userModel, child) { return Text(userModel.name ?? "", style: TextStyle( color: Colors.red, fontSize: 30 ) ); }, ), Consumer( builder: (_, userModel, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ userModel.changeName(); }, child: Text("改变值"), ), ); }, ), ], ), ), ); } } ``` 运行结果 ##### MultiProvider 在上面的例子中我们都只是返回了一个提供者,在实际开发过程中肯定会有多个提供者,我们虽然可以采用嵌套的方式来解决,但是这样无疑是混乱的,可读性级差。这个时候强大的MultiProvder就产生了,我们来看下示例: 第一步:创建两个模型 ``` import 'package:flutter/material.dart'; class UserModel1 with ChangeNotifier { String name = "Jimi"; void changeName() { name = "hello"; notifyListeners(); } } class UserModel4 with ChangeNotifier { String name = "Jimi"; int age = 18; void changeName() { name = "hello"; age = 20; notifyListeners(); } } ``` 第二步:应用程序入口设置 相对于方式一这种嵌套方式设置,方式二就显得尤为简单。 方式一:嵌套设置 ``` return ChangeNotifierProvider( create: (_) => UserModel1(), child: ChangeNotifierProvider( create: (_) => UserModel4(), child: MaterialApp( debugShowCheckedModeBanner: false, home: MultiProviderExample(), ), ), ); ``` 方式二:使用MultiProvider ``` return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => UserModel1()), ChangeNotifierProvider(create: (_) => UserModel4()), /// 添加更多 ], child: MaterialApp( debugShowCheckedModeBanner: false, home: MultiProviderExample(), ), ); ``` 第三步:使用共享数据 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_provider_example/user_model1.dart'; import 'package:flutter_provider_example/multi_provider_example/user_model4.dart'; import 'package:provider/provider.dart'; class MultiProviderExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("MultiProviderExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (_, userModel, child) { return Text(userModel.name, style: TextStyle( color: Colors.red, fontSize: 30 ) ); }, ), Consumer( builder: (_, userModel, child) { return Text(userModel.age.toString(), style: TextStyle( color: Colors.green, fontSize: 30 ) ); }, ), Consumer2( builder: (_, userModel1, userModel4, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ userModel1.changeName(); userModel4.changeName(); }, child: Text("改变值"), ), ); }, ), ], ), ), ); } } ``` 运行结果 ##### ProxyProvider 当我们有多个模型的时候,会有模型依赖另一个模型的情况,在这种情况下,我们可以使用ProxyProvider从另一个提供者获取值,然后将其注入到另一个提供者中。我们来看下代码演示 第一步:创建两个模型 下面我们创建了两个模型UserModel5和WalletModel,而WalletModel依赖与UserModel5,当调用WalletModel的changeName方法时会改变UserModel5里面的name,当然我们在实际开发的过程中并不是这么简单,这里只是演示模型依赖时如果使用ProxyProvider ``` import 'package:flutter/material.dart'; class UserModel5 with ChangeNotifier { String name = "Jimi"; void changeName({required String newName}) { name = newName; notifyListeners(); } } class WalletModel { UserModel5? userModel5; WalletModel({this.userModel5}); void changeName() { userModel5?.changeName(newName: "JIMI"); } } ``` 第二步:应用程序入口设置 ``` return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => UserModel5()), ProxyProvider( update: (_, userModel5, walletModel) => WalletModel(userModel5: userModel5), ) ], child: MaterialApp( debugShowCheckedModeBanner: false, home: ProxyProviderExample(), ), ); ``` 第三步:使用共享数据 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/proxy_provider_example/user_model5.dart'; import 'package:flutter_provider_example/proxy_provider_example/wallet_model.dart'; import 'package:provider/provider.dart'; class ProxyProviderExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ProxyProviderExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (_, userModel, child) { return Text(userModel.name, style: TextStyle( color: Colors.red, fontSize: 30 ) ); }, ), Consumer( builder: (_, userModel, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ userModel.changeName(newName: "hello"); }, child: Text("改变值"), ), ); }, ), Consumer( builder: (_, walletModel, child) { return Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ walletModel.changeName(); }, child: Text("通过代理改变值"), ), ); }, ), ], ), ), ); } } ``` 运行结果 ##### ChangeNotifierProxyProvider 和ProxyProvider原理一样,唯一的区别在于它构建和同步ChangeNotifier的ChangeNotifierProvider,当提供者数据变化时,将会重构UI。 下面我们给出一个例子: 获取书籍列表 获取收藏书籍列表 点击书籍可加入或者取消收藏 通过代理实时重构UI 第一步:创建两个模型 1、BookModel BookModel用户存储模型数据,将书籍转换成模型。 ``` class BookModel { static var _books = [ Book(1, "夜的命名数"), Book(2, "大奉打更人"), Book(3, "星门"), Book(4, "大魏读书人"), Book(5, "我师兄实在太稳健了"), Book(6, "深空彼岸"), ]; // 获取书籍长度 int get length => _books.length; // 根据ID获取书籍 Book getById(int id) => _books[id -1]; // 根据索引获取数据 Book getByPosition(int position) => _books[position]; // 更多.... } class Book { final int bookId; final String bookName; Book(this.bookId, this.bookName); } ``` 2、BookManagerModel BookManagerModel主要用于管理书籍、收藏书籍、取消收藏等操作 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart'; class BookManagerModel with ChangeNotifier { // 依赖bookModel final BookModel _bookModel; // 获取数据所有的ID List? _bookIds; // 构造函数 BookManagerModel(this._bookModel, {BookManagerModel? bookManagerModel}) : _bookIds = bookManagerModel?._bookIds ?? []; // 获取所有的书 List get books => _bookIds!.map((id) => _bookModel.getById(id)).toList(); // 根据索引获取数据 Book getByPosition(int position) => books[position]; // 获取书籍的长度 int get length => _bookIds?.length ?? 0; // 添加书籍 void addFaves(Book book) { _bookIds!.add(book.bookId); notifyListeners(); } // 删除书籍 void removeFaves(Book book) { _bookIds!.remove(book.bookId); notifyListeners(); } } ``` 第二步:应用程序入口设置 ``` return MultiProvider( providers: [ Provider(create: (_) => BookModel()), ChangeNotifierProxyProvider( create: (_) => BookManagerModel(BookModel()), update: (_, bookModel, bookManagerModel) => BookManagerModel(bookModel), ) ], child: MaterialApp( debugShowCheckedModeBanner: false, home: ChangeNotifierProxyProviderExample(), ), ); ``` 第三步:设置BottomNavigationBar ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/pages/page_a.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/pages/page_b.dart'; class ChangeNotifierProxyProviderExample extends StatefulWidget { @override _ChangeNotifierProxyProviderExampleState createState() => _ChangeNotifierProxyProviderExampleState(); } class _ChangeNotifierProxyProviderExampleState extends State { var _selectedIndex = 0; var _pages = [PageA(), PageB()]; @override Widget build(BuildContext context) { return Scaffold( body: _pages[_selectedIndex], bottomNavigationBar: BottomNavigationBar( currentIndex: _selectedIndex, onTap: (index) { setState(() { _selectedIndex = index; }); }, items: [ BottomNavigationBarItem( icon: Icon(Icons.book), label: "书籍列表" ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: "收藏" ) ], ), ); } } ``` 第四步:书籍列表UI构建 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_item.dart'; import 'package:provider/provider.dart'; class PageA extends StatelessWidget { @override Widget build(BuildContext context) { var bookModel = Provider.of(context); return Scaffold( appBar: AppBar( title: Text("书籍列表"), ), body: ListView.builder( itemCount: bookModel.length, itemBuilder: (_, index) => BookItem(id: index + 1), ), ); } } ``` 第五步:收藏列表UI构建 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_item.dart'; import 'package:provider/provider.dart'; class PageB extends StatelessWidget { @override Widget build(BuildContext context) { var bookManagerModel = Provider.of(context); var bookCount = bookManagerModel.length; return Scaffold( appBar: AppBar( title: Text("收藏列表"), ), body: ListView.builder( itemCount: bookCount, itemBuilder: (_, index) => BookItem(id: bookManagerModel.getByPosition(index).bookId), ), ); } } ``` 其他辅助封装类 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart'; import 'package:provider/provider.dart'; class BookButton extends StatelessWidget { final Book book; BookButton({ Key? key, required this.book }) : super(key: key); @override Widget build(BuildContext context) { var bookManagerModel = Provider.of(context); return GestureDetector( onTap: bookManagerModel.books.contains(this.book) ? () => bookManagerModel.removeFaves(this.book) : () => bookManagerModel.addFaves(this.book), child: SizedBox( width: 100, height: 60, child: bookManagerModel.books.contains(this.book) ? Icon(Icons.star, color: Colors.red,) : Icon(Icons.star_border), ), ); } } ``` ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart'; import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_button.dart'; import 'package:provider/provider.dart'; class BookItem extends StatelessWidget { final int id; BookItem({ Key? key, required this.id }) : super(key: key); @override Widget build(BuildContext context) { var bookModel = Provider.of(context); var book = bookModel.getById(id); return ListTile( leading: CircleAvatar( child: Text("${book.bookId}"), ), title: Text("${book.bookName}", style: TextStyle( color: Colors.black87 ), ), trailing: BookButton(book: book), ); } } ``` 运行结果 ##### ListenableProxyProvider ListenableProxyProvider是ListenableProvider的一个变体,但是在使用上和ChangeNotifierProvider效果惊人的一致,如果大家对ListenableProxyProvider有更深的理解,请联系我补充。 总结 Provider为我们提供了非常多的提供者,总共有八种。但我们比较常用的是ChangeNotifierProvider、MultiProvider、ChangeNotifierProxyProvider,关于其他的提供者可根据自己的实际应用场景来。 #### Flutter Provider状态管理---四种消费者使用分析 前言 在上一篇文章中我们对Provider的8种提供者进行了详细的描述以及用对应的案例说明他们的区别,那么这一节我们来聊一聊Provider的消费者,如果去优化你的项目结构以及它们的使用区别。 ##### Provider.of Provider.of(context)是Provider为我们提供的静态方法,当我们使用该方法去获取值的时候会返回查找到的最近的T类型的provider给我们,而且也不会遍历整个组件树,下面我们看下代码: 第一步:定义模型 我们定义了一个CountNotifier1的模型,后面所有的示例代码将围绕该模型来演示 ``` import 'package:flutter/material.dart'; class CountNotifier1 with ChangeNotifier { int count = 0; void increment() { count++; notifyListeners(); } } ``` 第二步:应用程序入口设置 ``` return ChangeNotifierProvider( create: (_) => CountNotifier1(), child: MaterialApp( debugShowCheckedModeBanner: false, home: ConsumerExample(), ), ); ``` 第三步:使用Provider.of 这里读取值和点击按钮+1时都是通过Provider.of()来获取及使用。 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/consumer_example/count_notifier1.dart'; import 'package:provider/provider.dart'; class ConsumerExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ConsumerExample"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Provider.of(context).count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only( top: 20 ), child: ElevatedButton( onPressed: (){ Provider.of(context).increment(); }, child: Text("点击加1"), ), ) ], ), ), ); } } ``` 错误日志 当我们运行代码的时候会提示一个报错,它提示说试图从Widget树外部监听提供者公开的值,如果要修复可以把listen改成false,这个问题其实是在Provider 4.0.2后会出现的,最主要的是它的默认行为就是ture,错误日志如下: ``` ======== Exception caught by gesture =============================================================== The following assertion was thrown while handling a gesture: Tried to listen to a value exposed with provider, from outside of the widget tree. This is likely caused by an event handler (like a button's onPressed) that called Provider.of without passing `listen: false`. To fix, write: Provider.of(context, listen: false); It is unsupported because may pointlessly rebuild the widget associated to the event handler, when the widget tree doesn't care about the value. The context used was: ConsumerExample(dependencies: [_InheritedProviderScope]) 'package:provider/src/provider.dart': Failed assertion: line 276 pos 7: 'context.owner!.debugBuilding || listen == false || debugIsInInheritedProviderUpdate' When the exception was thrown, this was the stack: ........ ==================================================================================================== ``` **设置listen为false** 1 Provider.of(context, listen: false).increment(); 运行结果 ##### Consumer Consumber只是在Widget中调用了Prvoider.of,并将其构造实现委托给了构造器,比如我们常见的Builder,如果你的Widget依赖多个模型,那么它还提供了Consumer23456方便调用,我们接下来对上面的案例采用Consumer来修改 用Consumer包裹组件 里面有个builder构造器,当我们把body改成下面重新运行后可以发现和使用Provider.of的结果一样,但是这里不需要在像使用Provider.of那样每次使用都要写一大串的重复性代码。 里面有三个属性: context: 当前的上下文 **Provider.of(context):** 模型对象 child: 子组件(不需要刷新的部分) ``` body: Consumer( builder: (_, CountNotifier1 countNotifier1, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(countNotifier1.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only( top: 20 ), child: ElevatedButton( onPressed: (){ countNotifier1.increment(); }, child: Text("点击加1"), ), ) ], ), ); }, ), ``` 优化Consumer 优化方式一:尽可能调整Consumer的位置 我们在上面的代码中发现Center以及Column组件也被Consumer包裹了进来,但是这两个组件是不需要更新状态的,而我们每次构建的Widget的时候,会重建整个body,所以我们优化一下代码结构,看起来就像下面这样: ``` body: Center( child: Consumer( builder: (_, CountNotifier1 countNotifier1, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( countNotifier1.count.toString(), style: TextStyle(color: Colors.red, fontSize: 50), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () { countNotifier1.increment(); }, child: Text("点击加1"), ), ), Container( child: Column( children: [ Text("更多组件1"), Text("更多组件2"), Text("更多组件3"), Text("更多组件4"), Text("更多组件5"), Text("更多组件6"), ], ), ) ], ), ); }, ) ) ``` 优化方式二:不需要刷新但被Consumer包裹的组件用child 比如上面我们有更多组件1-6甚至数百个组件无需刷新状态,但由于你用Consumer包裹会导致全部刷新,那么明显会导致性能的下降,你可能会想到单独用多个Consumer包裹需要刷新的组件就解决了,但这不就是本末倒置了吗,本身Provider是解决代码的健壮、重复的代码,所以这个时候我们可以采用Consumer为我们提供的child参数,如下: ``` body: Center( child: Consumer( builder: (_, CountNotifier1 countNotifier1, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( countNotifier1.count.toString(), style: TextStyle(color: Colors.red, fontSize: 50), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () { countNotifier1.increment(); }, child: Text("点击加1"), ), ), child! ], ), ); }, child: Container( child: Column( children: [ Text("更多组件1"), Text("更多组件2"), Text("更多组件3"), Text("更多组件4"), Text("更多组件5"), Text("更多组件6"), ], ), ), ) ), ``` ##### Selector Selector类和Consumer类似,只是对build调用Widget方法时提供更精细的控制,简单点来说,Selector也是一个消费者,它允许你可以从模型中准备定义哪些属性。 我们来举个例子: 比如,用户模型中有50个属性,但是我只需要更新年龄,这样我希望不需要重建用户名、电话号码等组件,那么Selector就是用于解决这个问题,我们看一下示例: 第一步:定义模型 ``` import 'package:flutter/material.dart'; class UserModel6 with ChangeNotifier { String name = "Jimi"; int age = 18; String phone = "18888888888"; void increaseAge() { age++; notifyListeners(); } } ``` 第二步:应用程序入口设置 ``` return ChangeNotifierProvider( create: (_) => UserModel6(), child: MaterialApp( debugShowCheckedModeBanner: false, home: SelectorExample(), ), ); ``` 第三步:使用Selector更精细的控制 ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/selector_example/user_model6.dart'; import 'package:provider/provider.dart'; class SelectorExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("SelectorExample"), ), body: Center( child: Selector( selector: (_, userModel6) => userModel6.age, builder: (_, age, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(age.toString(), style: TextStyle( color: Colors.red, fontSize: 30 ) ), child! ], ); }, child: Padding( padding: EdgeInsets.all(20), child: ElevatedButton( onPressed: (){ Provider.of(context, listen: false).increaseAge(); }, child: Text("改变年龄"), ), ), ), ), ); } } ``` 运行结果 ##### InheritedContext InheritedContext是Provider内置扩展了BuildContext,它不保存了组件在树中自己位置的引用,我们在上面的案例中见到Provider.of(context,listen: false),其实这个of方法就是使用Flutter查找树并找到Provider子类型为CountNotifier1而已。 三大方式: BuildContext.read: BuildContext.read()可以替换掉Provider.of(context,listen: false),它会找到CountNotifier1并返回它。 BuildContext.watch: BuildContext.watch()可以替换掉Provider.of(context,listen: false),看起来和read没有什么不同,但是使用watch你就不需要在使用Consumer。 BuildContext.select: BuildContext.watch()可以替换掉Provider.of(context,listen: false),看起来和watch也没有什么不同,但是使用select你就不需要在使用Selctor。 BuildContext.read 下面两种使用方式结果是一样的 使用Provider.of() ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// Provider.of 获取值 body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Provider.of(context).count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ), ], ), ), ); } } ``` 使用BuildContext.read ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// read 获取值 body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(context.read().count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ), ], ), ), ); } } ``` BuildContext.watch 使用Consumer ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// Consumer 获取值 body: Center( child: Consumer( builder: (_, countNotifier2, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(countNotifier2.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => countNotifier2.increment(), child: Text("点击加1"), ), ), ], ); }, ), ), ); } } ``` 使用BuildContext.watch ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget { @override Widget build(BuildContext context) { /// 重要 final countNotifier2 = context.watch(); return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// watch body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(countNotifier2.count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => countNotifier2.increment(), child: Text("点击加1"), ), ), ], ), ), ); } } ``` BuildContext.select 使用Selector ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// Selector body: Center( child: Selector( selector: (_, countNotifier2) => countNotifier2.count, builder: (_, count, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), child! ], ); }, child: Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ), ), ), ); } } ``` 使用BuildContext.select ``` import 'package:flutter/material.dart'; import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart'; import 'package:provider/provider.dart'; class InheritedContextExample extends StatelessWidget { @override Widget build(BuildContext context) { /// 重要 final count = context.select((CountNotifier2 countNotifier2) => countNotifier2.count); return Scaffold( appBar: AppBar( title: Text("InheritedContextExample"), ), /// select body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(count.toString(), style: TextStyle( color: Colors.red, fontSize: 50 ), ), Padding( padding: EdgeInsets.only(top: 20), child: ElevatedButton( onPressed: () => Provider.of(context, listen: false).increment(), child: Text("点击加1"), ), ) ], ), ), ); } } ``` 总结 Flutter为我们提供了多种读取值的方式,上面我们对消费者四大类的一个使用和分析对比,大家可根据自己的实际应用场景去使用对应的方式。 #### 安装教程