Flutter应用状态管理

前言

如下图,当state发生变化,根据你所定义的ui渲染函数重新渲染ui。但是每个组件有自己的状态,这些状态有的是独立的有的是互相统一的,如何管理呢?
在这里插入图片描述
在学习本文之前,首先要区分开临时状态和应用状态,通常有以下划分:

临时状态

其他组件不会获取此状态,不会以复杂形式变化

这种情况就是临时状态,你可以通过StatefulWidget来实现状态管理。
如:动画的进度,滚动条的位置,选项卡的选中项等。

应用状态

在APP的的多个组件之间,或者多个页面之间需要共享的状态称为应用状态。

这种情况就是应用状态,就涉及到了Flutter的应用状态管理。
如:用户登录信息,用户偏好,购物车信息等。

两者之间其实没有明确的分界线,如果你的APP中某个滚动条的位置会影响其他组件的显示状态,那这个就要定义为应用状态。

总之一句话,如果这块要给数据多个组件使用,就是应用状态;只给一个组件使用,就是临时状态

实战

以饿了么举例:

  1. 在商品列表里有我加购的信息。
    在这里插入图片描述

  2. 在购物车页面有我加购的信息
    在这里插入图片描述

  3. 在结算页面,有我加购的信息
    在这里插入图片描述
    那么,这个加购信息就应该是应用状态。

进一步分析

我们应该把购物车状态交给谁来管理呢?很显然,根据状态提升,应由他们统一的父组件来管理。
在这里插入图片描述
如上图,当用户点击MyListItem中的某一项时,会改变由MyApp管理的状态cart,进而引起MyCart随之改变。这样MyCart这块的代码只需要根据接受MyApp的的cart信息来更新UI,不需要考虑更新之类的代码,避免了很多不必要的麻烦。

另外,点击MyListItem怎么样把cart状态上传给MyApp呢?你可能会想这样干:在父组件维护一个cart数组,定义一些列操作cart的回调函数,然后把这些回调函数一层层传给子组件,子组件想要操作cart的时候,调用这些回调函数就可以啦。

@override
Widget build(BuildContext context) {
  return SomeWidget(
    //给MyListItem传一个回调函数
    MyListItem(myTapCallback),
  );
}

void myTapCallback(Item item) {
  //回调函数:添加到购物车
  添加的代码
}

是的,确实可以这样干。但是想一下,如果有很多状态需要在不同的地方进行操作,你就要定义很多很多回调函数,一层一层往下传。太麻烦了。

当然,Flutter也提供了InheritedWidget, InheritedNotifier, InheritedModel等组件可以将数据自顶往下传,但是这些只能限定于该组件之内的后代共享数据,不能与其他组件共享数据。

这里我们将使用provider库,简单易用。
首先在 pubspec.yaml添加依赖:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0

使用provider,你就不用考虑回调的问题,也不用考虑InheritedWidget的问题。但是你要明白一下三个概念:

  • ChangeNotifier

向他的订阅者发送消息通知。(类似Observable

class CartModel extends ChangeNotifier {
  final List<Item> _items = [];
  /// An unmodifiable view of the items in the cart.
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
  /// 总价格
  int get totalPrice => _items.length * 42;

  ///添加
  void add(Item item) {
    _items.add(item);
    // 数据变化后,通知订阅者去刷新页面
    notifyListeners();
  }

  /// 清除所有
  void removeAll() {
    _items.clear();
    notifyListeners();
  }
}
  • ChangeNotifierProvider

可以向他后代提供ChangeNotifier供其使用

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CartModel(),
      child: const MyApp(),
    ),
  );
}

如果要同时提供给多个给孩子,可以用MultiProvider

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => CartModel()),
        Provider(create: (context) => SomeOtherClass()),
      ],
      child: const MyApp(),
    ),
  );
}
  • Consumer

需要使用状态的地方

每次ChangeNotifier(本例是CartModel )变动都会通知Consumer执行builder

return Consumer<CartModel>(
  builder: (context, cart, child) {
    return Text("Total price: ${cart.totalPrice}");
  },
);

上面的代码中,第2个参数就是CartModel类型的对象。
第三个参数是一个优化项,如果你的一些组件比较大,某次变动以后,你不想整个刷新。可以参考如下代码:SomeExpensiveWidget类型的对象并不会每次都重新渲染。

return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
    children: [
      if (child != null) child,
      Text("Total price: ${cart.totalPrice}"),
    ],
  ),
  child: const SomeExpensiveWidget(),
);

注意

Consumer使用范围应该尽量小,不要这样做:

//这样的话会引起HumongousWidget全部重新渲染,这可能是不必要的
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget( 
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);

应该这样:

//这样只会影响重新渲染最里面的这个Consumer
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);

代码

至此,你就可以正常使用cart了:

1. 购物车页面获取已加购的商品列表
可以这样:
```java
class _CartList extends StatelessWidget {
	//如果cart有变动将会调用_CartList的build()
	...........
	Widget build(BuildContext context) {
		var cart = context.watch<CartModel>();
		...........
	}
}

或者:

//使用Consumer只会刷新Consumer包裹部分的build()
class _CartTotal extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
	....................
            Consumer<CartModel>(
                builder: (context, cart, child) =>
                    Text('\$${cart.totalPrice}', style: hugeStyle)),
	.....................
          ]
        ),
      ),
    );
  }
}
  1. 添加
    下面是商品列表中添加按钮的回调函数:使用了context.read()
    有时候只需要操作状态,并不关注状态的变化,即不用监听状态,可以用read()
onPressed: isInCart
 ? null//如果在购物车,什么都不执行
 : () {
     //不在,先获取然后执行,用的是context.read
     var cart = context.read<CartModel>();
     //context.read 等同于Provider.of<T>(this, listen: false);
     cart.add(item);
   },
  1. 判断是否在购物车中
    context.select() 允许你订阅你感兴趣的部分,只有感兴趣的部分变动时才会build
    var isInCart = context.select<CartModel, bool>(
    //select 返回的部分
      (cart) => cart.items.contains(item),
    );
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值