Flutter-State状态管理


状态管理是声明式编程非常重要的一个概念(非常重要!非常重要!非常重要!)。

一.为什么需要状态管理

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 举例及说明
  1. 创建自己需要共享的数据
    当使用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 接收网络数据

方法一:

  1. 在initState中正常请求网络数据
  2. 解析后的数据set给对应的ViewModel

方法二:

  1. 将网络请求写到ViewModel的构造方法中
  2. 在构造方法的最后,执行notifyListeners()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值