调用 Navigator Pop 或 Push 时触发 Flutter Stream Builder [英] Flutter Stream Builder Triggered when Navigator Pop or Push is Called

查看:22
本文介绍了调用 Navigator Pop 或 Push 时触发 Flutter Stream Builder的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在应用的主页/根页面中有一个流生成器.每当我在其他地方进行页面导航时都会触发此流构建器,这与流本身无关.

I have a stream builder in the app's home/root page. This stream builder gets triggered whenever I do a page-navigation elsewhere, which has nothing to do with the stream itself.

我的理解,根据 here这里,当页面在导航器中弹出/推送时,它会触发应用程序的重建,因此流构建器会重新连接,因此会触发.然而这似乎效率低下,那么有没有办法防止在弹出/推送页面时触发流构建器?

My understanding, according to here and here, is when a page is popped/pushed in the navigator, it triggers a rebuild on the app, so the stream builder gets re-attached and so it fires. However this seems inefficient, so is there a way to prevent the stream builder from firing when a page is popped/pushed?

此外,根据日志,当我推送页面时,页面首先构建并显示,然后流构建器被触发.然而,流构建器的小部件/页面根本不显示,即使日志/调试器清楚地显示流构建器的小部件已返回.它去哪儿了?它在 Flutter 框架中是如何工作的?

Additionally, according to the logs, when I push a page, the page is built and shown first, then the stream builder gets fired. However the stream builder's widget/page does not show at all, even though clearly the logs/debugger show that the stream builder's widget has been returned. Where did it go? How does it work in the Flutter framework?

以下是完整的代码和日志.该代码使用 Firebase 身份验证作为流构建器.

Below is the full code and logs. The code uses Firebase auth as a the stream builder.

代码:

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

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

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

class AppHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final FirebaseAuth auth = FirebaseAuth.instance;
    return StreamBuilder<FirebaseUser>(
      stream: auth.onAuthStateChanged,
      builder: (_, AsyncSnapshot<FirebaseUser> snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          final FirebaseUser user = snapshot.data;
          if (user == null) {
            debugPrint("User is NULL.");
            return SignInPage();
          } else {
            debugPrint("User exists.");
            return MainPage();
          }
        } else {
          debugPrint("In waiting state.");
          return Scaffold(
            body: Center(
              child: CircularProgressIndicator(),
            ),
          );
        }
      },
    );
  }
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    debugPrint("Building main page.");
    return Scaffold(
      body: Center(
        child: Text("Welcome to our app!"),
      ),
    );
  }
}

class SignInPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    debugPrint("Building sign-in page.");
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            FlatButton(
              color: Colors.blue,
              child: Text('Sign In as Anonymous'),
              onPressed: () {
                debugPrint("Anonymous");
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => MainPage()),
                );
              },
            ),
            FlatButton(
              color: Colors.red,
              child: Text('Sign In with Google'),
              onPressed: () => debugPrint("Google"),
            ),
          ],
        ),
      ),
    );
  }
}

日志,其中第 4 行表示按下按钮执行 navigator.pop():

Logs, where the 4th line indicates a button is pressed to do a navigator.pop():

I/flutter (22339): In waiting state.
I/flutter (22339): User is NULL.
I/flutter (22339): Building sign-in page.
I/flutter (22339): Anonymous
I/flutter (22339): Building main page.
I/flutter (22339): User is NULL.
I/flutter (22339): Building sign-in page.

推荐答案

我可以确认每次在我们的应用程序中导航时都会调用 StreamBuilder 中的 build 方法,这效率不高,因为它应该取消它的侦听器,创建一个新的一个并重建整个小部件.

I can confirm that the build method in StreamBuilder is called every time we navigate within our app, which is not efficient since it should cancel its listener, create a new one and rebuild the entire widget.

如果您的应用侦听身份验证状态以便在身份验证状态更改(加载/登录/主页)时显示适当的屏幕,您可能会遇到该问题

You may face that issue if your app listens to the authentication state in order to show an appropriate screen when auth state changes (Loading/Login/Home)

所以在大多数教程中,您会看到 StreamBuilder 是在无状态小部件的 build 方法中创建的.这不是一个有效的解决方案.

So in most of the tutorials, you will see that StreamBuilder is created in the build method in a Stateless widget. This is not an efficient solution.

改为使用 Stateful 小部件并在 initState()didChangeDependencies() 方法中监听您的身份验证更改.

Instead use Stateful widget and listen to your auth changes in initState() or didChangeDependencies() methods.

我们案例的不同之处在于,在 initState() 中,如果您使用 Provider,则在获取 Auth 服务时会遇到问题(上下文尚未准备好使用 Provided 服务).如果你不使用 Provider,你可以监听 initState() 的变化.但我强烈建议使用 Provider 来分离您的服务和页面.换句话说,使用 MVVM 模式,这样您的代码将具有可扩展性和可维护性.

The difference in our case would be that in initState() you will have issues in getting your Auth service if you use Provider (The context won't be ready with the Provided service yet). If you don't use Provider you can listen to changes in the initState(). But I highly recommend using Provider to separate your Services and Pages. In other words, use the MVVM pattern so your code will be scalable and maintainable.

class LandingScreen extends StatefulWidget {
  @override
  _LandingScreenState createState() => _LandingScreenState();
}

class _LandingScreenState extends State<LandingScreen> {
  @override
  Widget build(BuildContext context) {
      return SplashView();
  }

  @override
  void didChangeDependencies() {
      //we don't have to close or unsubscribe SB
        Provider.of<AuthService>(context, listen: false).streamAuthServiceState().listen((state){
          switch (state) {
            case AuthServiceState.Starting:
            print("starting");
              break;
            case AuthServiceState.SignedIn:
              Navigator.pushReplacementNamed(context, Routes.HOME);
              break;
            case AuthServiceState.SignedOut:
              Navigator.pushReplacementNamed(context, Routes.LOGIN);
              break;
            default:
              Navigator.pushReplacementNamed(context, Routes.LOGIN);
          }
        });

    super.didChangeDependencies();
  }
}

如果您将直接使用 Firebase 流 - 将我的流替换为 FirebaseAuth.instance.onAuthStateChanged

If you'll use directly Firebase stream - replace my stream with FirebaseAuth.instance.onAuthStateChanged

这篇关于调用 Navigator Pop 或 Push 时触发 Flutter Stream Builder的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆