如果使用的 memoizer 没有通知监听器,Future builder 将永远运行

Future builder runs forever, if memoizer used doesnt notify to the listerners

我正在尝试使用以下代码根据共享首选项中存储的值切换抽屉选项卡。

当不使用 memoizer 但未来的构建器 运行 永远使用时,代码工作正常。

如果我使用存储器未来构建器仍然 运行s 至少两次(不是永远),但是获取和设置函数不起作用,新值不会更新,也不会通知小部件。

我需要一些方法来永远停止 运行ning future builder 并通过触发其中存在的 get 和 set 函数相应地通知用户

通知程序class

class SwitchAppProvider extends ChangeNotifier {


  switchApp(value) async {

     
        // initialize instance of sharedpreference
        SharedPreferences prefs = await SharedPreferences.getInstance();

        prefs.setBool('key', value);

        notifyListeners();
     
  }


Future<bool?> getValue() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final value = prefs.getBool('key');
    return value;
  }
}

抽屉

Widget _buildDrawer() {
  return ChangeNotifierProvider<SwitchAppProvider>(
    create: (context) => SwitchAppProvider(),
    child: Consumer<SwitchAppProvider>(
      builder: (context, provider, _) {
        return Container(
          width: 260,
          child: Drawer(
            child: Material(
              color: Color.fromRGBO(62, 180, 137, 1),
              child: ListView(
                children: <Widget>[
                  Container(
                    padding: AppLandingView.padding,
                    child: Column(
                      children: [
                        const SizedBox(height: 10),
                        FutureBuilder(
                          future: provider.getValue(),
                          builder: (BuildContext context,
                              AsyncSnapshot<dynamic> snapshot) {
                            if (snapshot.data == true) {
                              return _buildMenuItem(
                                text: 'widget1',
                                icon: Icons.add_business,
                                onTap: () {
                                  provider.switchApp(false);
                                },
                              );
                            } else {
                              return _buildMenuItem(
                                text: 'widget2',
                                icon: Icons.add_business,
                                onTap: () {
                                  provider.switchApp(true);
                                },
                              );
                            }
                          },
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    ),
  );
}

脚手架

@override
Widget build(BuildContext context) {
  return Scaffold(
    drawer: _buildDrawer(),
  );
}

更新

我进一步分析,问题出在provider.getValue(),如果我在返回值future builder 运行s forever

之前使用notifyListeners()

我删除了它,未来的构建器不会永远 运行,但它不会更新其他小部件。

场景是

小部件 1

  1. 包含一个抽屉
  2. 有一个按钮可以切换应用程序
  3. 使用共享首选项设置点击值(setValue() 函数)并通知侦听器
  4. 在小部件 1 中,通知程序运行良好,并在点击调用 setValue() 时更改抽屉按钮选项。
  5. 小部件 1 中的所有内容都已解决,因为它调用 setValue() 因此触发 notifyListeners() 并重新呈现小部件 1

小部件 2

  1. 仅从共享首选项中获取值(getValue() 函数)。 getValue 函数不能使用 notifyListeners(),如果使用 futurebuilder 是 运行ning forever

  2. 小部件 2 不设置任何值,因此它不使用 setValue(),因此它不会收到通知

  3. 如何在小部件 1 中触发 setValue() 时通知小部件 2

即 widget1 使用 setValue() 函数设置应用程序

widget2 从 getValue() 函数获取值并得到通知

更新 2

class SwitchAppProvider with ChangeNotifier {
   dynamic  _myValue;

  dynamic get myValue => _myValue;

   set myValue(dynamic newValue) {
     _myValue = newValue;
     notifyListeners();
   }

  setValue(value) async {
    // initialize instance of sharedpreference
    SharedPreferences prefs = await SharedPreferences.getInstance();

     await  prefs.setBool('key', value);

    notifyListeners();
  }

  SwitchAppProvider(){
    getValue();
  }

  Future<void> getValue() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
     myValue = prefs.getBool('key');


  }


}


小部件 2

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
        value: SwitchAppProvider(),

        child:  Consumer<SwitchAppProvider>(
            builder: (BuildContext context, SwitchAppProvider provider, _) {



              if (provider.myValue == true) {
                return Center(
                  child: CircularProgressIndicator(),
                );
              } else {
                return Container(
                    child: Text('provider.myValue'));
              }
            })
    );
  }
}

_buildMenuItem

  // helper widget to build item of drawer
  Widget _buildMenuItem({
    required String text,
    required IconData icon,
    required GestureTapCallback onTap,
  }) {
    final color = Colors.white;
    final hoverColor = Colors.white;

    return ListTile(
      leading: Icon(icon, color: color),
      title: Text(text, style: TextStyle(color: color, fontSize: 18)),
      hoverColor: hoverColor,
      onTap: onTap,
    );
  }

“如果我使用存储器,未来构建器仍然至少运行两次(不是永远),但是获取和设置函数不起作用,新值不会更新,也不会通知小部件。”

这是预期的行为:

An AsyncMemoizer is used when some function may be run multiple times in order to get its result, but it only actually needs to be run once for its effect. 

所以prefs.setBool('key', value);只在第一次执行。

你肯定不想用它。

如果您编辑代码以删除 AsyncMemoizer,我们可以尝试进一步帮助您。

更新后编辑

你是对的,getValue() 函数应该 通知监听器,如果它这样做,那么监听器将重建并再次请求该值,这将通知听众,这将重建并再次询问该值,这......(你明白了)。

你的推理有问题。 widget1widget2 不被通知, Consumer 被通知。这将重建一切。代码相当复杂,通过删除不需要的小部件可以简化很多。

我会建议你

  1. await prefs.setBool('isWhatsappBusiness', value); 在通知听众之前。
  2. 查看 this answer 以了解类似问题。

编辑 3

我不知道你做错了什么,但这行得通:

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

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

class SwitchAppProvider extends ChangeNotifier {
  switchApp(value) async {
    // initialize instance of sharedpreference
    SharedPreferences prefs = await SharedPreferences.getInstance();

    await prefs.setBool('key', value);

    notifyListeners();
  }

  Future<bool?> getValue() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final value = prefs.getBool('key');
    return value;
  }
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        drawer: _buildDrawer(),
      ),
    );
  }

  Widget _buildDrawer() {
    return ChangeNotifierProvider<SwitchAppProvider>(
      create: (context) => SwitchAppProvider(),
      child: Consumer<SwitchAppProvider>(
        builder: (context, provider, _) {
          return SizedBox(
            width: 260,
            child: Drawer(
              child: Material(
                color: const Color.fromRGBO(62, 180, 137, 1),
                child: ListView(
                  children: <Widget>[
                    Column(
                      children: [
                        const SizedBox(height: 10),
                        FutureBuilder(
                          future: provider.getValue(),
                          builder: (BuildContext context,
                              AsyncSnapshot<dynamic> snapshot) {
                            print('Am I building?');
                            if (snapshot.data == true) {
                              return ListTile(
                                tileColor: Colors.red[200],
                                title: const Text('widget1'),
                                leading: const Icon(Icons.flutter_dash),
                                onTap: () {
                                  provider.switchApp(false);
                                },
                              );
                            } else {
                              return ListTile(
                                tileColor: Colors.green[200],
                                title: const Text('widget2'),
                                leading: const Icon(Icons.ac_unit),
                                onTap: () {
                                  provider.switchApp(true);
                                },
                              );
                            }
                          },
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

如果仍然无法正常工作,则问题出在其他地方。

编辑 4

首先,我建议你在以后的问题中更加清楚。立即编写所有需要的代码并删除不需要的小部件。避免以相同的方式命名不同的事物而造成的混淆。

第二个小部件没有更新,因为它正在侦听不同的通知程序。

当你这样做时

return ChangeNotifierProvider.value(
        value: SwitchAppProvider(),

Widget2 中你正在创建一个新的提供者对象,你没有监听你在 Drawer 中创建的提供者的变化。

您需要将 ChangeNotifierProvider.value 小部件移至小部件树中更高的位置,并使用相同的小部件:

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

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

class SwitchAppProvider extends ChangeNotifier {
  switchApp(value) async {
    // initialize instance of sharedpreference
    SharedPreferences prefs = await SharedPreferences.getInstance();

    await prefs.setBool('key', value);

    notifyListeners();
  }

  Future<bool?> getValue() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    final value = prefs.getBool('key');
    return value;
  }
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChangeNotifierProvider<SwitchAppProvider>(
        create: (context) => SwitchAppProvider(),
        child: Scaffold(
          appBar: AppBar(),
          drawer: _buildDrawer(),
          body: const Widget2(),
        ),
      ),
    );
  }

  Widget _buildDrawer() {
    return Consumer<SwitchAppProvider>(builder: (context, provider, _) {
      return SizedBox(
        width: 260,
        child: Drawer(
          child: Material(
            color: const Color.fromRGBO(62, 180, 137, 1),
            child: ListView(
              children: <Widget>[
                Column(
                  children: [
                    const SizedBox(height: 10),
                    FutureBuilder(
                      future: provider.getValue(),
                      builder: (BuildContext context,
                          AsyncSnapshot<dynamic> snapshot) {
                        print('Am I building?');
                        if (snapshot.data == true) {
                          return ListTile(
                            tileColor: Colors.red[200],
                            title: const Text('widget1'),
                            leading: const Icon(Icons.flutter_dash),
                            onTap: () {
                              provider.switchApp(false);
                            },
                          );
                        } else {
                          return ListTile(
                            tileColor: Colors.green[200],
                            title: const Text('widget2'),
                            leading: const Icon(Icons.ac_unit),
                            onTap: () {
                              provider.switchApp(true);
                            },
                          );
                        }
                      },
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      );
    });
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Consumer<SwitchAppProvider>(
      builder: (BuildContext context, SwitchAppProvider provider, _) {
        return FutureBuilder(
          future: provider.getValue(),
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            print('Am I building even more ?');
            if (snapshot.data == true) {
              return const Center(
                child: CircularProgressIndicator(),
              );
            } else {
              return const Text('provider.myValue');
            }
          },
        );
      },
    );
  }
}