百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Flutter——状态管理(Provider)_flutter进程保活

toyiye 2024-04-03 23:10 29 浏览 0 评论

上一节介绍了通过state来进行状态管理,我们知道了通过state方式管理组件内部状态和组件与子组件之间的状态。但是我们试想一下,如果我们把所有的状态、状态对应的业务方法如果都写在StatefulWidget中,这样的架构是很难进行进行模块化和组件化编码,耦合性太高,难于维护,更不适合大型工程化项目。

那么Flutter有提供什么方式能帮我们解决以上问题吗?Flutter自身提供了InheritedWidget,这一章节我们先不具体讲解,这一节主要介绍通过Provider方式进行状态管理。

基础

Provider概念

Provider是一个由社区构建的状态管理包,而不是Google推出,但ProviderGoogle极力推荐的状态管理方式之一,也就是说Provider是Flutter的一个第三方插件,它其实是对InheritedWidget组件进行了封装,使其更易用,更易复用。

Provider优势

我们为什么要用Provider而不是直接使用InheritedWidget,我们看下官方介绍

1. 简化的资源分配与处置

2. 懒加载

3. 创建新类时减少大量的模板代码

4. 支持 DevTools更通用的调用 InheritedWidget 的方式

5. 提升类的可扩展性,

6. 整体的监听架构时间复杂度以指数级增长(如 ChangeNotifier, 其复杂度为 O(N))

Provider类结构图

基本使用

  1. 首先在pubspec.yaml中添加Provider依赖
dependencies:
  flutter:
    sdk: flutter

  provider: ^6.1.1
  1. 定义需要共享的数据
class CountNotifier with ChangeNotifier {
  int count = 0;
  void increment() {
    count++;
    notifyListeners();
  }
}
  1. 进行初始化
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => CountNotifier(),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: ProviderCountExample(),
      ),
    );
  }
}
  1. 创建StatefulWidget并使用共享数据
class ProviderCountExample extends StatefulWidget {
  @override
  _ProviderCountExampleState createState() => _ProviderCountExampleState();
}

class _ProviderCountExampleState extends State<ProviderCountExample> {

  @override
  Widget build(BuildContext context) {

    final counter = Provider.of<CountNotifier>(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
          ),
        ),
      ),
    );
  }
}

通过以上代码,即可实现点击按钮,数字增加的效果。上面代码中的很多方法和类,我们目前可能还很陌生,但是通过上面的代码已经实现了将所有的状态和业务都放到一个独立的类中进行管理,如果我们做过Android客户端开发,这种架构和google目前推荐的MVVM架构是很类似的。

其实Provider是一种生产者-消费者的设计模式,接下来带大家进一步加深对Provider理解和使用。

提供者

Provider

通用的 Provider 类型,可以用于任何类型的数据,包括基本类型、对象和函数。可以使用它为组件树中的任何位置提供值,但是当该值更改的时候,它并不会更新UI。

代码示例:

//定义一个状态管理类
class UserModel {
  String name = "Flutter";
  void changeName() {
    name = "Hello";
  }
}

//程序入口或者界面build方法增加
return Provider<UserModel>(
  create: (_) => UserModel(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ProviderExample(),
  ),
);

// 增加一个StatelessWidget组件,包含一个文本和按钮
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<UserModel>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                );
              },
            ),
            Consumer<UserModel>(
              builder: (_, userModel, child) {
                return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("改变值"),
                  ),
               );
              },
            ),
          ],
        ),
      ),
    );
  }
}

当我们运行代码之后,点击按钮修改UserModel中的name值,但是该数据改变之后UI并没有变化也没有重建,那是因为Provider提供者组件不会监听它提供的值的变化。

ChangeNotifierProvider

它跟Provider组件不同,ChangeNotifierProvider会监听模型对象的变化,而且当数据改变时,它也会重建Consumer(消费者),下面我们给出一个示例。

// 定义一个状态管理类,混入ChangeNotifier
class UserModel with ChangeNotifier {
  String name = "Flutter";
  void changeName() {
    name = "Hello";
    notifyListeners();
  }
}

//程序入口或者StatefulWidget界面build方法增加
return ChangeNotifierProvider<UserModel>(
  create: (_) => UserModel(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ChangeNotifierProviderExample(),
  ),
);

// 增加一个StatelessWidget组件,包含一个文本和按钮
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<UserModel>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                );
              },
            ),
            Consumer<UserModel>(
              builder: (_, userModel, child) {
                return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

当我们运行代码之后,点击按钮修改UserModel中的name值,文本会从"Flutter"变为"Hello"。

FutureProvider

用于管理未来(Future),可以在异步操作完成后提供数据。适用于需要等待异步操作完成后获取数据的情况,如初始化数据。FutureProvider有一个初始值,子组件可以使用该Future值并告诉子组件使用新的值来进行重建。

注意:

  • FutureProvider只会重建一次
  • 默认显示初始值,然后显示Future值
  • 最后不会再次重建

示例代码如下:

class UserModel{
  UserModel({this.name});
  String? name = "Flutter";
  Future<void> changeName() async {
    await Future.delayed(Duration(milliseconds: 2000));
    name = "Hello";
  }
}

class UserFuture {
  Future<UserModel2> asyncGetUserModel() async {
    await Future.delayed(Duration(milliseconds: 2000));
    return UserModel(name: "Future数据");
  }
}

//FutureProvider
return FutureProvider<UserModel>(
  initialData: UserModel(name: "hello"),
  create: (_) => UserFuture().asyncGetUserModel2(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: FutureProviderExample(),
  ),
);

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<UserModel>(
              builder: (_, userModel, child) {
                return Text(userModel.name ?? "",
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                );
              },
            ),
            Consumer<UserModel>(
              builder: (_, userModel, child) {
                return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

运行以上代码,界面会从"hello"变成"Future数据",但是点击按钮,虽然调用了changeName方法,UI并不会刷新,这也应证了FutureProvider只会重建一次。

StreamProvider

用于管理流(Stream),可以监听流中数据的变化,并在数据发生变化时更新部件。适用于从网络或其他异步源获取数据的情况,如实时更新数据。

示例代码如下:

class UserModel{
  UserModel3({this.name});
  String? name = "Flutter";
  void changeName() {
    name = "Hello";
  }
}

class UserStream {
  Stream<UserModel> getStreamUserModel() {
    return Stream<UserModel>.periodic(Duration(milliseconds: 1000),
        (value) => UserModel(name: "$value")
    ).take(10);
  }
}

//增加StreamProvider
return StreamProvider<UserModel3>(
  initialData: UserModel(name: "hello"),
  create: (_) => UserStream().getStreamUserModel(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: StreamProviderExample(),
  ),
);

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<UserModel>(
              builder: (_, userModel, child) {
                return Text(userModel.name ?? "",
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                );
              },
            ),
            Consumer<UserModel>(
              builder: (_, userModel, child) {
                return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

运行以上代码,界面会从开始的"Hello"-> "0" -> "1"..."9",但是点击按钮,并不会变成"Hello",如果流的数据是持续的,那么会一直显示的是流里面的值。

MultiProvider

在上面的例子中我们都只是返回了一个提供者,在实际开发过程中肯定会有多个提供者,我们虽然可以采用嵌套的方式来解决,但是这样无疑是混乱的,可读性级差。这个时候强大的MultiProvder就产生了,我们来看下示例:

class UserModel1 with ChangeNotifier {
  String name = "Jimi";
  void changeName() {
    name = "hello";
    notifyListeners();
  }
}

class UserModel4 with ChangeNotifier {
  String name = "Flutter";
  int age = 18;
  void changeName() {
    name = "hello";
    age = 20;
    notifyListeners();
  }
}

return MultiProvider(
  providers: [
    ChangeNotifierProvider<UserModel1>(create: (_) => UserModel1()),
    ChangeNotifierProvider<UserModel4>(create: (_) => UserModel4()),
    /// 添加更多
  ],
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: MultiProviderExample(),
  ),
);

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<UserModel1>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                );
              },
            ),
            Consumer<UserModel4>(
              builder: (_, userModel, child) {
                return Text(userModel.age.toString(),
                    style: TextStyle(
                        color: Colors.green,
                        fontSize: 30
                    )
                );
              },
            ),
            Consumer2<UserModel1, UserModel4>(
              builder: (_, userModel1, userModel4, child) {
                return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel1.changeName();
                      userModel4.changeName();
                    },
                    child: Text("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

ProxyProvider

是 Provider 的通用版本,可以根据其他 Provider 的值来构建和更新任何类型的数据。适用于根据其他状态动态生成任意类型数据的情况,具有更大的灵活性。

当我们有多个模型的时候,会有模型依赖另一个模型的情况,在这种情况下,我们可以使用ProxyProvider从另一个提供者获取值,然后将其注入到另一个提供者中。ChangeNotifierProxyProvider和ListenableProxyProvider,它们工作的方式是相似的。

消费者

上面我们主要介绍了提供者,但是很多代码也使用了消费者,即数据读取方式。那么Provider提供了哪些消费者呢?

Provider.of

Provider.of<T>(context)Provider为我们提供的静态方法,当我们使用该方法去获取值的时候会返回查找到的最近的T类型的provider给我们,而且也不会遍历整个组件树,下面我们看下代码:

class CountNotifier1 with ChangeNotifier {
  int count = 0;
  void increment() {
    count++;
    notifyListeners();
  }
}

return ChangeNotifierProvider(
  create: (_) => CountNotifier1(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ConsumerExample(),
  ),
);

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<CountNotifier1>(context, listen: false).count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(
                top: 20
              ),
              child: ElevatedButton(
                onPressed: (){
                  Provider.of<CountNotifier1>(context, listen: false).increment();
                },
                child: Text("点击加1"),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Consumer

Consumber只是在Widget中调用了Prvoider.of,并将其构造实现委托给了构造器,比如我们常见的Builder,如果你的Widget依赖多个模型,那么它还提供了Consumer23456方便调用。具体使用代码就不列举了,上面的提供者中的示例代码都基本使用的是Consumer方式。

Selector

Selector类和Consumer类似,只是对build调用Widget方法时提供更精细的控制,简单点来说,Selector也是一个消费者,它允许你可以从模型中准备定义哪些属性。

class MyDataProvider extends ChangeNotifier {
  String _data = 'Initial Data';

  String get data => _data;

  void updateData(String newData) {
    _data = newData;
    notifyListeners(); // 通知监听器进行更新
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Provider Selector Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              // 使用 Selector 小部件选择性地监听数据
              Selector<MyDataProvider, String>(
                selector: (context, myData) => myData.data,
                builder: (context, data, _) {
                  return Text('Data from Provider: $data');
                },
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  // 更新数据
                  Provider.of<MyDataProvider>(context, listen: false)
                      .updateData('New Data');
                },
                child: Text('Update Data'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

在这个示例中,我们首先创建了一个 MyDataProvider 类来管理数据,并使用 ChangeNotifierProvider 将其注册为根部件。然后,我们在 UI 中使用 Selector 小部件选择性地监听数据,并在数据变化时更新相关的 UI。最后,通过点击按钮来更新数据,观察 Selector 中的 UI 是否会更新。

InheritedContext

InheritedContextProvider内置扩展了BuildContext,它保存了组件在树中自己位置的引用,我们在上面的案例中见到Provider.of<CountNotifier1>(context,listen: false),其实这个of方法就是使用Flutter查找树并找到Provider子类型为CountNotifier1而已。

三大方式:

  • BuildContext.read: BuildContext.read<CountNotifier1>()可以替换掉Provider.of<CountNotifier1>(context,listen: false),它会找到CountNotifier1并返回它。
  • BuildContext.watch: BuildContext.watch<CountNotifier1>()可以替换掉Provider.of<CountNotifier1>(context,listen: false),看起来和read没有什么不同,但是使用watch你就不需要在使用Consumer。
  • BuildContext.select: BuildContext.select<CountNotifier1>()可以替换掉Provider.of<CountNotifier1>(context,listen: false),看起来和watch也没有什么不同,但是使用select你就不需要在使用Selector。
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: [
            // 这段代码替换了Provider.of<CountNotifier1>(context,listen: false)
            Text(context.read<CountNotifier1>().count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier1>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

小结

在 Flutter 中,Provider 是一个强大的状态管理库,它提供了一种简单而有效的方式来在应用程序中共享和管理状态。以下是关于 Flutter Provider 的小结:

提供者类型

  • Provider 提供了多种类型的提供者,包括 ChangeNotifierProvider、StreamProvider、FutureProvider 等,用于满足不同场景下的状态管理需求。

消费者类型:

  • Provider提供了多种类型的消费者,包括Provider.of、Consumer、Selector、InheritedContext,用于不同场景下读取状态数据。

数据共享

  • 使用 Provider 可以轻松地在整个应用程序中共享状态,无需手动传递数据。
  • 提供者将状态存储在应用程序的组件树中,可以在任何地方访问和更新状态。

简化代码

  • Provider 可以帮助简化代码,减少模板代码的编写量。
  • 它通过依赖注入的方式提供了一种简洁而优雅的方式来管理和访问状态。

性能优化

  • 使用 Provider 可以实现针对性的 UI 更新,减少不必要的重建,提高应用程序的性能。
  • 可以使用 Selector 小部件选择性地监听特定数据,只有在数据发生变化时才会重建相关的 UI。

适用场景

  • Provider 适用于各种规模的 Flutter 应用程序,从简单的示例应用到复杂的生产级应用程序都可以使用。
  • 它特别适用于需要跨多个小部件共享状态的情况,如用户身份认证信息、主题、语言设置等。

综上所述,Flutter Provider 是一个功能强大且易于使用的状态管理库,通过使用 Provider,可以轻松地共享和管理状态,从而提高应用程序的可维护性和可扩展性。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码