【问题标题】:How does listeners in Provider work in Flutter?Provider 中的监听器在 Flutter 中是如何工作的?
【发布时间】:2020-09-17 00:01:16
【问题描述】:

我正在使用 Provider 包来处理来自不同位置的数据。对于这个问题,我创建了一个示例项目,其中包含一个显示登录按钮的欢迎屏幕,单击后,它会重定向到登录屏幕,其中有登录文本字段,但现在,我放置了一个单击调用的按钮login 函数,并且该 login 函数通知侦听器。我还有一个注销功能和布尔值,用于跟踪登录状态。

这些函数和值驻留在 lib 文件夹的 providers 目录中名为 auth.provider.dart 的文件中。

import 'package:flutter/foundation.dart';

class Auth with ChangeNotifier {
  var _loggedIn = false;

  bool get loggedIn => _loggedIn;

  void loginUser() {
    _loggedIn = true;
    print('Logged in');
    notifyListeners();
  }

  void logout() {
    _loggedIn = false;
    print('Logged out');
    notifyListeners();
  }
}

我在 main.dart 中提供提供者并使用消费者:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Auth>(
      create: (_) => Auth(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: Consumer<Auth>(
          builder: (_, auth, __) {
            print('main.dart logged in: ${auth.loggedIn}');
            if(auth.loggedIn) return HomeScreen();
            return WelcomeScreen();
          },
        ),
        routes: {
          LoginScreen.routeName: (ctx) => LoginScreen(),
          HomeScreen.routeName: (ctx) => HomeScreen(),
        },
      ),
    );
  }
}

在上面的文件中,我只是检查loggedIn 是否为真。如果不是,则显示欢迎屏幕,以便用户可以登录,但如果用户已登录,则直接显示主屏幕。还在 Consumer 的构建器中放置了一条打印语句,以便在收到有关身份验证数据的任何更改的通知时打印状态。这会在应用启动时运行一次,然后再也不运行。

登录界面没有太多内容,只有一个脚手架和一个调用登录功能的登录按钮。

RaisedButton(
    child: Text('Click to Login'),
    onPressed: () {
      Provider.of<Auth>(context, listen: false).loginUser();
    },
),

现在单击登录按钮时,它将loggedIn 布尔值设置为true,并通知应该是Consumer 的侦听器,但main.dart 中的Consumer 永远不会收到通知。它不会触发更改,有一个条件是检查登录是否为真,然后显示主屏幕,但屏幕根本没有改变。必须手动使用 Navigator 移动到主屏幕,并且在主屏幕上有一个注销按钮,它只是将 loggedIn 设置为 false,一旦发生这种情况,它应该通知消费者,但它再次没有。

现在让我想到一个问题:Provider 中的侦听器是如何工作的?它何时通知侦听器进行更改?如果更改发生在上面的示例中超过一个级别,它是否不会通知,Consumer 在 main.dart 中并且更改发生在以下两个级别main.dart &gt; WelcomeScreen &gt; LoginScreen。如何让main.dart中的Consumer知道它使用的底层数据发生了变化?

如果你想快速上手上面的例子,here 是存储库链接。

【问题讨论】:

    标签: flutter dart flutter-provider


    【解决方案1】:

    我浏览了你的 github 代码。

    使用 pushReplacementNamed 进入登录屏幕

    最初->main.dart->welcomeScreen->(on-clicking-loginbtn)->LoginScreen

    这里,

    1) 消费者没有在听(因为您最初的 main.dart 屏幕被登录页面取代,并且不存在于当前要收听的页面上)

    2)没有导航器可以返回(因为页面被替换不在堆栈中)

    使用 pushNamed 进入登录屏幕

    最初->main.dart->welcomeScreen->(on-clicking-loginbtn)->LoginScreen

    这里,

    1) 消费者正在收听(因为您的初始 main.dart 屏幕未被替换并且当前存在于页面上。但是您没有看到更改,因为您已经在堆栈中覆盖了带有登录屏幕的主初始页面)

    2)有导航器可以返回主屏幕(现在您可以使用返回箭头返回查看更改)

    结论如果页面被其他页面替换,页面将无法监听更改的 Provider Value。它应该在屏幕监听中。

    可以使用的模式

    ma​​in.dart(home 参数)

    home: Consumer<Auth>(
              builder: (_, auth, __) {
                print('main.dart logged in: ${auth.loggedIn}');
                if(auth.loggedIn) return HomeScreen();
                return WelcomeScreen();
              },
            )
    

    WelcomeScreen(登录代码)

     RaisedButton(
                  child: Text('Login'),
                  onPressed: () => Navigator.of(context).pushReplacementNamed(LoginScreen.routeName),
                )
    

    LoginScreen(登录按钮)

    RaisedButton(
              child: Text('Click to Login'),
              onPressed: () {
                Provider.of<Auth>(context, listen: false).loginUser();
                Navigator.of(context).pushReplacementNamed('/');
              },
            )
    

    主屏幕(注销 btn)

    RaisedButton(
              child: Text('Logout'),
             onPressed: () {
                Provider.of<Auth>(context, listen: false).logout();
                Navigator.of(context).pushReplacementNamed('/');
              }
    

    【讨论】:

    • 不会pushNamed 像在生产应用程序中那样在堆栈上创建无限页面,登录页面将可以选择转到注册页面以及忘记密码页面,并且所有这些页面都将相互链接。为了防止出现这种情况,我使用了pushReplacementNamed
    • 是的,你是对的。我不是指您使用 pushNamed。我只是在解释不同的场景。在这种情况下使用提供者是没有用的。你可以简单地使用导航。当您想要多个小部件中的某些全局状态时使用提供程序。
    • 我已向您的仓库发送拉取请求。你可以去看看
    • 之所以使用Provider,是因为我登录后可以在任意页面获取用户数据。例如 - 要在 Firestore 中存储或读取某些内容,我需要访问当前登录的用户。如果我不在main.dart 中使用Provider,我怎么知道用户是否登录并相应地显示正确的屏幕?因此,为了保持一致性,我使用了 Provider,因为无论如何我都必须在其他屏幕上使用它。
    • 我已经更新了我的答案。它将登录信息存储在提供程序中,并根据提供程序值完成导航。希望它能解决你的问题。我将提供拉取请求(如果您需要完整代码)
    猜你喜欢
    • 2021-02-01
    • 2022-10-14
    • 2021-09-02
    • 2022-01-22
    • 2020-12-06
    • 2018-10-03
    • 1970-01-01
    • 2020-08-29
    • 2021-07-02
    相关资源
    最近更新 更多