了解 listen: false 与 Provider<SomeType>.of(context, listen: false) 一起使用时的工作原理

Posted

技术标签:

【中文标题】了解 listen: false 与 Provider<SomeType>.of(context, listen: false) 一起使用时的工作原理【英文标题】:Understand how listen: false works when used with Provider<SomeType>.of(context, listen: false) 【发布时间】:2020-02-23 07:55:11 【问题描述】:

我正在了解 Provider 包如何与 Flutter 一起工作,但对 listen:false 的工作方式感到困惑。

我使用来自一个新 Flutter 项目的常用 Counter 示例编写了一些基本代码。我创建了三种类型的无状态小部件,每一种都使用 Provider:

    Provider.of(context) 消费者 Provider.of(context, listen: false)

第三个例子是展示如何在不重建的情况下访问提供者对象并调用它的方法。

当我运行应用程序时,所有小部件的数量都在变化 - 我只希望它在前两个中发生变化。

这是一个简单的例子——我做错了什么?

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

void main() => runApp(MyApp());

class Counter with ChangeNotifier 
  int _count = 0;
  int get count => _count;

  void increment() 
    _count++;
    notifyListeners();
  


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Counter()),
      ],
      child: MaterialApp(
        title: 'Provider Demo',
        theme: ThemeData(
          primarySwatch: Colors.amber,
        ),
        home: MyHomePage(title: 'Provider Demo Home Page'),
      ),
    );
  


class MyHomePage extends StatelessWidget 
  MyHomePage(Key key, this.title) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) 
    Counter counter = Provider.of<Counter>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ExampleProviderWidget(),
            ExampleConsumerWidget(),
            ExampleNoListenWidget()
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  


class ExampleProviderWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    Counter counter = Provider.of<Counter>(context);

    return Container(
      color: Colors.green,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context):',
            ),
            Text(
              '$counter.count',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  


class ExampleConsumerWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Consumer<Counter>(
      builder: (context, counter, _) 
        return Container(
          color: Colors.blue,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Consumer<Counter>(context):',
                ),
                Text(
                  '$counter.count',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
        );
      ,
    );
  


class ExampleNoListenWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    Counter counter = Provider.of<Counter>(context, listen: false);

    return Container(
      color: Colors.red,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context, listen: false):',
            ),
            Text(
              '$counter.count',
              style: Theme.of(context).textTheme.display1,
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: () => counter.increment(),
            )
          ],
        ),
      ),
    );
  


【问题讨论】:

【参考方案1】:

这是因为,虽然用listen:false 调用Provider.of 的小部件不想重建,但它的父级却强迫它重建。

在您的示例中,当 Counter 更改时,MyHomePage 会重建并重新创建指定 listen:false 的小部件,这反过来又会强制它也重建。

MyHomePage 也应在此处指定listen: false

【讨论】:

感谢雷米的快速回复。这是有道理的,并且在按照建议更改代码后它可以工作。再次感谢【参考方案2】:

雷米建议的是一种可行的方法(hack)。 但我相信在这种情况下更好的方法是删除电话 Counter counter = Provider.of(context);来自 MyHomePageClass。该调用正在污染您的 Counter 对象的范围。 正如 flutter.io 所建议的那样,It is best practice to put your Consumer widgets as deep in the tree as possible。正如您在 ExampleProviderWidget()、ExampleConsumerWidget()、ExampleNoListenWidget() 中所做的那样。所以让floatingActionButton 成为一个单独的小部件类并拥有自己的Provider.of(context)。 并且您的 MyHomePageClass 将不必调用 Provider.of。

【讨论】:

【参考方案3】:

对我有用

I/flutter (12384): MyApp build
I/flutter (12384): MyHomePage build
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleConsumerWidget build
I/flutter (12384): ExampleNoListenWidget build
Reloaded 1 of 524 libraries in 792ms.
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleProviderWidget build


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

void main() => runApp(MyApp());

class Counter with ChangeNotifier 
  int _count = 0;
  int get count => _count;

  void increment() 
    _count++;
    notifyListeners();
  


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    print('MyApp build');
    return MaterialApp(
      title: 'Provider Demo',
      theme: ThemeData(
        primarySwatch: Colors.amber,
      ),
      home: MyHomePage(title: 'Provider Demo Home Page'),
    );
  


class MyHomePage extends StatelessWidget 
  MyHomePage(Key key, this.title) : super(key: key);

  final String title;
  Counter _counter = Counter();
  @override
  Widget build(BuildContext context) 
    print('MyHomePage build');
    return ChangeNotifierProvider.value(
      value: _counter,
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ExampleProviderWidget(),
              ExampleConsumerWidget(),
              ExampleNoListenWidget()
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => _counter.increment(),
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  


class ExampleProviderWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print('ExampleProviderWidget build');
    Counter counter = Provider.of<Counter>(context);

    return Container(
      color: Colors.green,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context):',
            ),
            Text(
              '$counter.count',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  


class ExampleConsumerWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print('ExampleConsumerWidget build');
    return Consumer<Counter>(
      builder: (context, counter, _) 
        return Container(
          color: Colors.blue,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Consumer<Counter>(context):',
                ),
                Text(
                  '$counter.count',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
        );
      ,
    );
  


class ExampleNoListenWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    print('ExampleNoListenWidget build');
    Counter counter = Provider.of<Counter>(context, listen: false);

    return Container(
      color: Colors.red,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context, listen: false):',
            ),
            Text(
              '$counter.count',
              style: Theme.of(context).textTheme.display1,
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: () => counter.increment(),
            )
          ],
        ),
      ),
    );
  

【讨论】:

【参考方案4】:

listen: false 必须能够调用 [State.initState] 内的 Provider.of 或提供者的 create 方法。

listen: true,后面的值更改会触发新的 [State.build] 到小部件,并触发 [State.didChangeDependencies] 到 [StatefulWidget]。

【讨论】:

【参考方案5】:

从您编写的代码来看,您在 onPressed 方法中使用的 increment 方法将不起作用,如果您实现 listen: false.

【讨论】:

以上是关于了解 listen: false 与 Provider<SomeType>.of(context, listen: false) 一起使用时的工作原理的主要内容,如果未能解决你的问题,请参考以下文章

INSTALL_FAILED_CONFLICTING_PROVIDER

ANDROID Installation error: INSTALL_FAILED_CONFLICTING_PROVIDER

day17_Listener与Filter

vue中$attrs $listeners你会用吗?

android——Installation error: INSTALL_FAILED_CONFLICTING_PROVIDER 解决方案

动态查询的传参测试,关于#和$在Integer下传0..o(^▽^)o