一、简介

在Flutter开发中,或多或少的都会设计到页面的多状态管理,如果大家对Flutter技术比较熟悉的话,那么应该知道下面的一些状态管理框架,像Bloc,Getx我都用过,整体来说再状态管理不是很复杂的情况下还是可以的。

在这里插入图片描述
接下来,我们来看一下Flutter官方推荐的状态管理框架Provider是如何使用的。Flutter 针对不同类型对象提供了多种不同的 Provider;Provider 也是借助了 InheritWidget,将共享状态放到顶层 MaterialApp 之上;

  • setState能刷新widget子树,刷新范围太大,并且需要把数据对象传递到子类。
  • InheritedWidget不用传递数据对象,通过context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();获取父类的数据。但是刷新范围大,只能由上而下传递。
  • Provider可以实现局部刷新。只要数据对象改变,UI能自动变化,实现响应式编程。屏蔽刷新逻辑,实现响应式数据与UI的绑定。无论是子类或父类改变数据都能刷新绑定的UI。

在这里插入图片描述

二、基本使用

使用之前,需要先在pubspec.yaml添加provider

dependencies:
  flutter:
    sdk: flutter

  provider: ^6.0.2

2.1 Provider基本使用

Provider是一款基于数据流的观察者模式,使用的第一步就是新建一个继承自ChangeNotifier的数据管理类。下面,我们来看一下官方的例子使用Provider方式如何实现。

import 'package:flutter/cupertino.dart';

class CountProviderModel extends ChangeNotifier {
  
  int _count = 0;
  int get count => _count;

  void increment() {
    print("increment");
    _count++;
    notifyListeners();
  }
}

在CountProviderModel类中,我们定义了有一个数据增加的方法,最后还调用notifyListeners发送通知。接着,我们新建一个测试页面,该页面的根部使用ChangeNotifierProvider组件进行包裹,需要刷新的地方使用Consumer组件进行包裹,用于消费

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

import 'count_provider.dart';

class ProviderPage extends StatelessWidget {

  const ProviderPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CountProviderModel>(
      create: (_) => CountProviderModel(),
      builder: (context, child) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("ProviderPage"),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text('You have pushed the button this many times:'),
                Consumer<CountProviderModel>(
                  builder: (context, notifier, child) {
                    return Text("${notifier.count}",style: const TextStyle(
                      fontSize: 24,
                    ), );
                  },
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => context.read<CountProviderModel>().increment(),
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        );
      },
    );
  }
}

最后,我们修改一下Flutter项目的入口main文件。

// 改写 main.dart
import 'package:flutter/material.dart';
import 'package:stateresearch/pages/ProviderPage.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter状态管理',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProviderPage(),
    );
  }
}

重新运行项目,点击右下角的按钮时数字就会自动加。

2.2 跨页面状态共享

作为一个全局的状态管理框架,跨页面的状态共享是必须的,为了方便说明,我们再新建两个页面ProviderPageTwo和ProviderPageThree,对应的代码如下:
ProviderPageTwo.dart

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

import 'count_provider.dart';

class ProviderPageTwo extends StatelessWidget {

  const ProviderPageTwo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("ProviderPageTwo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Consumer<CountProviderModel>(
              builder: (context, notifier, child) {
                return Text("${notifier.count}",style: const TextStyle(
                  fontSize: 24,
                ));
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<CountProviderModel>().increment();
          // 2秒后跳转至新的页面
          Future.delayed(const Duration(seconds: 2), () {
            Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
              return ProviderPageThree();
            }));
          });
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

ProviderPageThree.dart

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

import 'count_provider.dart';

class ProviderPageThree extends StatelessWidget {
  const ProviderPageThree({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("ProviderPageThree"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Consumer<CountProviderModel>(
              builder: (context, notifier, child) {
                return Text("${notifier.count}",style: const TextStyle(
                  fontSize: 24,
                ));
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Provider.of<CountProviderModel>(context, listen: false).increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

接着,我们在App顶层进行全局监听,因此,其他页面无需 ChangeNotifierProvider也可以获取 Model。

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  ProviderPageTwo(),
    );
  }
}

2.3 多Model全局共享

除了前面的两种使用场景,Provider还支持多Model的状态共享。首先,我们再新建一个ProviderModel类。

import 'package:flutter/material.dart';

class ListProviderModel extends ChangeNotifier {

  final List<String> _list = [];
  List<String> get list => _list;

  void push(String value) {
    _list.add(value);
    notifyListeners();
  }
}

然后,我们修改ProviderPageTwo和ProviderPageThree两个页面,对应的代码如下:
ProviderPageTwo.dart

import 'dart:math';

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

import 'count_provider.dart';
import 'list_provider.dart';

class ProviderPageTwo extends StatelessWidget {

  const ProviderPageTwo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("ProviderPageTwo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Consumer<CountProviderModel>(
              builder: (context, notifier, child) {
                return Text("${notifier.count}");
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<CountProviderModel>().increment();
          context.read<ListProviderModel>().push("List-${Random().nextInt(10)}");
          Future.delayed(const Duration(seconds: 2), () {
            Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
              return const ProviderPageThree();
            }));
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

ProviderPageThree.dart

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

import 'count_provider.dart';
import 'list_provider.dart';

class ProviderPageThree extends StatelessWidget {
  const ProviderPageThree({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("ProviderPageThree"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Consumer<CountProviderModel>(
              builder: (context, notifier, child) {
                return Text("${notifier.count}");
              },
            ),
            Consumer<ListProviderModel>(
              builder: (context, notifier, child) {
                return Text("${notifier.list}");
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Provider.of<CountProviderModel>(context, listen: false).increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

最后,我们修改main入口文件的代码,使用MultiProvider 包裹多个Model,如下。

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CountProviderModel()),
        ChangeNotifierProvider(create: (_) => ListProviderModel()),
      ],
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  ProviderPageTwo(),
    );
  }
}

三、总结

通过前面的例子可以看到,使用Provider时我们需要先新建一个Model对象,它继承自ChangeNotifier,是一个被观察的对象,当Model对象改变时需要调用notifyListeners通知观察者刷新。下面是ChangeNotifier的源码:

class ChangeNotifier implements Listenable {
  ObserverList<VoidCallback>? _listeners = ObserverList<VoidCallback>();

  @protected
  bool get hasListeners {
    return _listeners!.isNotEmpty;
  }

  @override
  void addListener(VoidCallback listener) {
    _listeners!.add(listener);
  }

  @override
  void removeListener(VoidCallback listener) {
    _listeners!.remove(listener);
  }

  @mustCallSuper
  void dispose() {
    _listeners = null;
  }

  @protected
  @visibleForTesting
  void notifyListeners() {
    if (_listeners != null) {
      final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners!);
      for (final VoidCallback listener in localListeners) {
        try {
          if (_listeners!.contains(listener))
            listener();
        } catch (exception, stack) {
          ......
        }
      }
    }
  }
}

ChangeNotifier把方法添加到数组中,然后调用notifyListeners时会通知观察者实现,整个工作流程示意图如下。
在这里插入图片描述


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》