一、简介
在Flutter开发中,或多或少的都会设计到页面的多状态管理,如果大家对Flutter技术比较熟悉的话,那么应该知道下面的一些状态管理框架,像Bloc,Getx我都用过,整体来说再状态管理不是很复杂的情况下还是可以的。
接下来,我们来看一下Flutter官方推荐的状态管理框架Provider是如何使用的。Flutter 针对不同类型对象提供了多种不同的 Provider;Provider 也是借助了 InheritWidget,将共享状态放到顶层 MaterialApp 之上;
- setState能刷新widget子树,刷新范围太大,并且需要把数据对象传递到子类。
- InheritedWidget不用传递数据对象,通过
context.dependOnInheritedWidgetOfExactType
获取父类的数据。但是刷新范围大,只能由上而下传递。(); - Provider可以实现局部刷新。只要数据对象改变,UI能自动变化,实现响应式编程。屏蔽刷新逻辑,实现响应式数据与UI的绑定。无论是子类或父类改变数据都能刷新绑定的UI。
二、基本使用
使用之前,需要先在pubspec.yaml添加provider
dependencies:
flutter:
sdk: flutter
provider: ^6.0.2
2.1 Provider基本使用
Provider是一款基于数据流的观察者模式,使用的第一步就是新建一个继承自ChangeNotifier的数据管理类。下面,我们来看一下官方的例子使用Provider方式如何实现。
import 'package:flutter/cupertino.dart';
class CountProviderModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
print("increment");
_count++;
notifyListeners();
}
}
在CountProviderModel类中,我们定义了有一个数据增加的方法,最后还调用notifyListeners发送通知。接着,我们新建一个测试页面,该页面的根部使用ChangeNotifierProvider组件进行包裹,需要刷新的地方使用Consumer组件进行包裹,用于消费
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
class ProviderPage extends StatelessWidget {
const ProviderPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CountProviderModel(),
builder: (context, child) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPage"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Consumer(
builder: (context, notifier, child) {
return Text("${notifier.count}",style: const TextStyle(
fontSize: 24,
), );
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}
最后,我们修改一下Flutter项目的入口main文件。
// 改写 main.dart
import 'package:flutter/material.dart';
import 'package:stateresearch/pages/ProviderPage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter状态管理',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ProviderPage(),
);
}
}
重新运行项目,点击右下角的按钮时数字就会自动加。
2.2 跨页面状态共享
作为一个全局的状态管理框架,跨页面的状态共享是必须的,为了方便说明,我们再新建两个页面ProviderPageTwo和ProviderPageThree,对应的代码如下:
ProviderPageTwo.dart
import 'package:flutter/material.dart';
import 'package:flutter_app/provider_three_page.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
class ProviderPageTwo extends StatelessWidget {
const ProviderPageTwo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageTwo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Consumer(
builder: (context, notifier, child) {
return Text("${notifier.count}",style: const TextStyle(
fontSize: 24,
));
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read().increment();
// 2秒后跳转至新的页面
Future.delayed(const Duration(seconds: 2), () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return ProviderPageThree();
}));
});
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
ProviderPageThree.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
class ProviderPageThree extends StatelessWidget {
const ProviderPageThree({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageThree"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Consumer(
builder: (context, notifier, child) {
return Text("${notifier.count}",style: const TextStyle(
fontSize: 24,
));
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of(context, listen: false).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
接着,我们在App顶层进行全局监听,因此,其他页面无需 ChangeNotifierProvider也可以获取 Model。
void main() {
runApp(ChangeNotifierProvider(
create: (_) => CountProviderModel(),
child: MyApp(),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProviderPageTwo(),
);
}
}
2.3 多Model全局共享
除了前面的两种使用场景,Provider还支持多Model的状态共享。首先,我们再新建一个ProviderModel类。
import 'package:flutter/material.dart';
class ListProviderModel extends ChangeNotifier {
final List _list = [];
List get list => _list;
void push(String value) {
_list.add(value);
notifyListeners();
}
}
然后,我们修改ProviderPageTwo和ProviderPageThree两个页面,对应的代码如下:
ProviderPageTwo.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_app/provider_three_page.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
import 'list_provider.dart';
class ProviderPageTwo extends StatelessWidget {
const ProviderPageTwo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageTwo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Consumer(
builder: (context, notifier, child) {
return Text("${notifier.count}");
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read().increment();
context.read().push("List-${Random().nextInt(10)}");
Future.delayed(const Duration(seconds: 2), () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return const ProviderPageThree();
}));
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
ProviderPageThree.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
import 'list_provider.dart';
class ProviderPageThree extends StatelessWidget {
const ProviderPageThree({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageThree"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Consumer(
builder: (context, notifier, child) {
return Text("${notifier.count}");
},
),
Consumer(
builder: (context, notifier, child) {
return Text("${notifier.list}");
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of(context, listen: false).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
最后,我们修改main入口文件的代码,使用MultiProvider 包裹多个Model,如下。
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CountProviderModel()),
ChangeNotifierProvider(create: (_) => ListProviderModel()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProviderPageTwo(),
);
}
}
三、总结
通过前面的例子可以看到,使用Provider时我们需要先新建一个Model对象,它继承自ChangeNotifier,是一个被观察的对象,当Model对象改变时需要调用notifyListeners通知观察者刷新。下面是ChangeNotifier的源码:
class ChangeNotifier implements Listenable {
ObserverList? _listeners = ObserverList();
@protected
bool get hasListeners {
return _listeners!.isNotEmpty;
}
@override
void addListener(VoidCallback listener) {
_listeners!.add(listener);
}
@override
void removeListener(VoidCallback listener) {
_listeners!.remove(listener);
}
@mustCallSuper
void dispose() {
_listeners = null;
}
@protected
@visibleForTesting
void notifyListeners() {
if (_listeners != null) {
final List localListeners = List.from(_listeners!);
for (final VoidCallback listener in localListeners) {
try {
if (_listeners!.contains(listener))
listener();
} catch (exception, stack) {
......
}
}
}
}
}
ChangeNotifier把方法添加到数组中,然后调用notifyListeners时会通知观察者实现,整个工作流程示意图如下。