Provider简介

Flutter Provider是Flutter中一个非常流行的状态管理库,它可以帮助开发者更加方便地管理应用程序中的状态。Provider提供了一种简单的方式来共享和管理应用程序中的数据,并且可以根据数据的变化来自动更新UI界面。

Provider的核心思想是将数据作为一个全局的单例对象,然后通过InheritedWidget的上下文来共享这个对象。当数据发生变化时,Provider会通知依赖它的UI组件进行更新。这种设计模式非常适合Flutter应用程序中的状态管理,因为它可以避免使用全局变量和回调函数来管理状态。

使用

在Provider中,我们需要定义一个数据模型类,这个类通常包含了我们需要共享的一些数据和状态。例如,一个计数器应用程序的数据模型类可能如下所示:

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

在这个数据模型类中,我们定义了一个名为CounterModel的类,并继承了ChangeNotifier类,这个类是Provider库中提供的一个基类,它实现了通知UI组件更新的功能。我们还定义了一个私有的计数器变量_count和一个公有的计数器变量count,以及一个increment方法用于增加计数器的值,并调用notifyListeners方法来通知UI组件更新。

接下来,在我们的应用程序中,我们需要使用Provider来共享这个CounterModel对象。这可以通过Provider的of方法来实现,例如:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: MaterialApp(
        home: CounterPage(),
      ),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<CounterModel>(context);
    return Scaffold(
      appBar: AppBar(title: Text('Counter Example')),
      body: Center(
        child: Text('${counter.count}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

在这个例子中,我们在MyApp类中创建了一个ChangeNotifierProvider对象,并将CounterModel对象作为create方法的返回值传递进去。然后,在CounterPage类中,我们使用Provider.of方法获取CounterModel对象,并将其传递给UI组件。当用户点击浮动按钮时,我们调用CounterModel的increment方法来增加计数器的值,并且由于我们已经使用了Provider来共享数据,所以UI组件会自动更新显示计数器的值。

Flutter Provider是一个非常方便和强大的状态管理库,它可以帮助我们更加方便地管理应用程序中的状态,并且可以避免一些常见的状态管理问题。

Provider(create: (_) => MyModel(), child: ...) 是Provider库中的一个构造函数,用于创建一个共享MyModel对象的Provider。这个构造函数有两个参数:

  1. create: 一个回调函数,用于创建MyModel对象。这个回调函数的参数是BuildContext对象,但在这个例子中,我们没有使用这个参数,所以使用了一个下划线(_)来表示它是一个未使用的参数。在这个回调函数中,我们可以创建并返回MyModel对象。

  2. child: 一个Widget,它是Provider的子节点。在这个例子中,我们没有提供具体的Widget,所以使用了省略号(...)表示这是需要替换成其他的Widget的占位符。

当我们使用Provider(create: (_) => MyModel(), child: ...)构造函数创建一个Provider时,Provider库会自动将MyModel对象共享给所有使用Provider.of<MyModel>(context)方法的Widget。这意味着,当我们在应用程序中的任何地方调用Provider.of<MyModel>(context)时,我们都可以获取到同一个MyModel对象的实例。如果我们在MyModel对象中修改了数据,这些变化将自动通知依赖它的Widget进行更新。

注意

需要注意的是,Provider的作用域是有限的。也就是说,当我们在Provider的子树之外的Widget中调用Provider.of<MyModel>(context)时,它将会抛出一个异常。因此,在使用Provider时,我们需要将它放在需要共享数据的Widget的父节点上,以确保Provider的作用域覆盖所有需要使用共享数据的Widget。

总结来说,Provider(create: (_) => MyModel(), child: ...)是一个用于创建共享MyModel对象的Provider的构造函数,它可以帮助我们更加方便地管理应用程序中的状态,并且可以根据数据的变化来自动更新UI界面。

踩坑

遇到的错误

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';


class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter Page'),
      ),
      body: ChangeNotifierProvider(
        create: (context) => CounterModel(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'You have pushed the button this many times:',
              ),
              Consumer<CounterModel>(
                builder: (context, counter, child) => Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.headlineMedium,
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<CounterModel>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

以上源码报错 Error: Could not find the correct Provider<CounterModel> above this TestPage Widget

image.png

原因分析

这个错误通常是由于没有正确的将 ChangeNotifierProvider 注册在 TestPage 的父级 widget 中引起的。在这种情况下,您需要确保 TestPage 的父级 widget 包括 ChangeNotifierProvider。

解决方法

一种解决方法是将 ChangeNotifierProvider 注册在 MaterialApp 的顶级 widget 中,如下所示:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: MaterialApp(
        title: 'My App',
        home: TestPage(),
      ),
    );
  }
}

在这个例子中,ChangeNotifierProvider 注册在 MyApp widget 中,并将 CounterModel 提供给整个应用程序。这样,当 TestPage 被创建时,它将能够访问 CounterModel 实例。

如果您不想在 MyApp 中注册 ChangeNotifierProvider,则可以将其注册在 TestPage 的父级 widget 中。例如,您可以创建一个新的 widget 并将其包装在 ChangeNotifierProvider 中,然后将该 widget 用作 TestPage 的父级 widget,如下所示:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: TestPage(),
    );
  }
}

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Consumer<CounterModel>(
              builder: (context, counter, child) => Text(
                '${counter.count}',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<CounterModel>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

在这个例子中,我们创建了一个新的 widget MyHomePage,并将 ChangeNotifierProvider 包装在其中。然后,我们将 TestPage 用作 MyHomePage 的子 widget,并在 TestPage 中访问 CounterModel。这样,当 TestPage 被创建时,它将能够访问 CounterModel 实例。

修改后的结果

按这个思路修改后,按+按钮,没有报错了,计数能正常刷新了。 image.png