文章目录
状态管理是声明式编程非常重要的一个概念(非常重要!非常重要!非常重要!)。
一.为什么需要状态管理
1.1 什么是状态管理
Flutter作为声明式编程(Flutter、Vue、React等),有大量的State需要来进行管理。页面是通过State的改变来进行刷新的。(状态其实就是数据,包括业务数据+状态标识)
1.2 不同状态管理分类
1.2.1 短时状态Ephemeral state
什么样的是短时状态:某些状态只需要在自己的Widget中使用即可
例如:主页BottomNavigationBar中当前被选中的tab
class HomeIPhonePage extends StatefulWidget {
@override
_HomeIPhonePageState createState() => _HomeIPhonePageState();
}
class _HomeIPhonePageState extends State<HomeIPhonePage> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: homeTabPages,
),
短时状态处理方式:短时状态只需要使用StatefulWidget对应的State类自己管理即可,Widget树中的其它部分并不需要访问这个状态。
1.2.2 应用状态App state
什么样的是应用状态:需要在多个部分进行共享
- 比如用户的一个个性化选项(主题颜色,比如手机或pad)
- 比如用户的登录状态信息
- 比如在Widget之间传递的状态(构造函数传递orEventBus传递)
不使用状态管理前会出现的问题:各种状态在Widget之间相互传递,代码的耦合度会越来越高,代码编写质量、后期维护、可扩展性都非常差
应用状态处理方式:使用全局状态管理的方式,来对状态进行统一的管理和应用
1.2.3 如何选择不同的管理方式
开发中,没有明确的规则去区分哪些状态是短时状态,哪些状态是应用状态。某些短时状态可能在之后的开发维护中需要升级为应用状态。
官方建议的分类原则:
开发原则:可以将所有状态都当作应用状态进行管理,但是不建议这样做
经验原则:怎么方便怎么操作
二、共享状态管理(应用状态)
以一般的首页为例:某信息在父组件中产生,多个子组件需要用到,其它页面也需要用到
2.1 InheritedWidget
作用:InheritedWidget可以实现跨组件数据的传递
步骤:
(举例中的StateDataWidget是一个示例名称,开发中根据具体使用情况自己定义对应名称)
1.定义一个共享数据的StateDataWidget,需要继承自InheritedWidget
2.实现抽象方法updateShouldNotify
3.创建静态方法返回StateDataWidget(外部调用该方法获取StateDataWidget对象)
4.创建需要共享的状态
5.找到合适的位置使用该StateDataWidget
将StateDataWidget放置在父Widget上,以可以放到更高层的祖先Widget上
6.将祖先用StateDataWidget包裹
import 'package:flutter/cupertino.dart';
//1.创建共享状态widget,该widget需要继承InheritedWidget
class StateDataWidget extends InheritedWidget{
//2.创建需要共享的状态
//widget中的属性都是不能改变的,所以如果外部需要改变这个值,那么需要构造器重新构造widget
final int counter;
//3.自定义构造方法
//重写构造方法,因为默认构造方法不能传递widget
StateDataWidget({this.counter,Widget child}):super(child: child);
//4.创建静态方法获取StateDataWidget,用于外部调用
static StateDataWidget of(BuildContext context){
//作用:沿着Element树,去找到最近的StateDataElement,从Element中找到对应Widget
return context.dependOnInheritedWidgetOfExactType();
}
//5.决定要不要回调State中的didChangeDependencies
//从该方法的名称可以看出"当更新的时候要不要通知",问题:通知谁?
//如果返回true:执行依赖当前的InheritedWidget的State中的didChangeDependencies
@override
bool updateShouldNotify(StateDataWidget oldWidget) {
return counter != oldWidget.counter;
}
}
寻找StateDataWidget放置位置的一些建议:
2.2 Provider
2.2.1 重要概念
- ChangeNotifier:真正数据(状态)存放的地方
- ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier
- Consumer:Widget树中需要使用数据(状态)的地方
2.2.2 基本步骤
1.创建自己需要共享的数据
2.在应用程序的顶层创建ChangeNotifierProvider
3.在其它位置使用共享数据
2.2.3 举例及说明
- 创建自己需要共享的数据
当使用Provider时,实际上是一种MVVM架构。
ViewModel需要继承自ChangeNotifier,在状态改变时通知倾听者。
import 'package:flutter/cupertino.dart';
//1.创建自己需要共享的数据
class CounterViewModel extends ChangeNotifier {
int _counter;
int get counter => _counter;
set counter(int value) {
_counter = value;
notifyListeners();
}
}
2.在应用程序的顶层创建ChangeNotifierProvider
void main() {
runApp(
ChangeNotifierProvider(
create: (ctx) => CounterViewModel(),
child: MyApp(),
)
);
}
3.在其它位置使用共享数据
- 方法一:Provider.of
场景/作用:使用数据
特点:当Provider中的数据发生改变时,Provider.of所在的widget整个build方法都会重新构建
class HomeHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
//3.在其它位置使用共享数据
int counter = Provider.of<CounterViewModel>(context).counter;
print("HomeHeader的build方法");
return Container(
color: Colors.red,
child: Text(
"Header:$counter",
style: TextStyle(fontSize: 30),
),
);
}
}
- 方法二:Consumer
场景/作用:(1)使用数据 (2)修改数据(widget依赖数据)
特点1:(相对推荐)当Provider中的数据发生改变时,只会执行Consumer的build方法
class HomeContent extends StatefulWidget {
@override
_HomeContentState createState() => _HomeContentState();
}
class _HomeContentState extends State<HomeContent> {
@override
Widget build(BuildContext context) {
print("HomeContent的build方法");
//3.在其它位置使用共享数据
return Card(
color: Colors.blue,
child: Consumer<CounterViewModel>(
builder: (ctx,counterVM,child){
print("HomeContent的Consumer方法");
return Text(
"Content:${counterVM.counter}",
style: TextStyle(fontSize: 30),
);
},
)
);
}
}
特点2:防止UI过度重绘
floatingActionButton: Consumer<CounterViewModel>(
builder: (ctx,counterVM,child){
print("floatingActionButton的Consumer方法");
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
counterVM.counter += 1;
},
);
},
)
上述代码中的Icon(Icons.add)是不需要重新绘制的
floatingActionButton: Consumer<CounterViewModel>(
builder: (ctx,counterVM,child){
print("floatingActionButton的Consumer方法");
return FloatingActionButton(
child: child,
onPressed: (){
counterVM.counter += 1;
},
);
},
child: Icon(Icons.add),
)
- 方法三:Selector
场景/作用:widget不依赖数据,只是要修改数据
(1)selector方法:对原有的数据进行转换
(2)shouldRebuild:要不要重新构建
floatingActionButton: Selector<CounterViewModel,CounterViewModel>(
selector: (ctx,counterVM) => counterVM,
shouldRebuild: (pre,next) => false,
builder: (ctx,counterVM,child){
print("floatingActionButton的Consumer方法");
return FloatingActionButton(
child: child,
onPressed: (){
counterVM.counter += 1;
},
);
},
child: Icon(Icons.add),
)
2.2.4 开发中使用
2.2.4.1 代码规范相关
1.主要目录结构
|–model
|–data_model
|–view_model
|–initialize_providers(唯一文件,providerList)
|–data_view_model
2.创建data_model
class UserData{
final String token;
final String name;
UserData({this.token,this.name});
factory UserData.fromJson(Map<String,dynamic> json){
return UserData(
token: json['token'],
name: json['name']
);
}
}
3.创建data_view_model
import 'package:flutter/cupertino.dart';
import 'package:flutter_app/provider/model/user_data.dart';
class UserDataViewModel extends ChangeNotifier{
UserData _userData;
UserData get userData => _userData;
set userData(UserData value) {
_userData = value;
}
UserDataViewModel(this._userData);
}
4.创建initialize_providers
import 'package:flutter_app/provider/model/user_data.dart';
import 'package:flutter_app/provider/view_model/user_data_view_model.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'counter_view_model.dart';
List<SingleChildWidget> providers = [
ChangeNotifierProvider(create: (ctx)=> CounterViewModel()),
ChangeNotifierProvider(create: (ctx)=> UserDataViewModel(UserData(token:"this is token",name :"这里是名字"))),
];
5.使用MultiProvider创建ChangeNotifierProvider
MultiProvider(
providers: providers,
child: MyApp(),
)
2.2.4.2 数据复合使用
body: Center(
child: Consumer2<CounterViewModel,UserDataViewModel>(
builder: (ctx,counterVM,userDataVM,child){
return Text("两部分共享数据${counterVM.counter}和${userDataVM.userData.name}");
},
),
),
2.2.4.3 接收网络数据
方法一:
- 在initState中正常请求网络数据
- 解析后的数据set给对应的ViewModel
方法二:
- 将网络请求写到ViewModel的构造方法中
- 在构造方法的最后,执行notifyListeners()