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

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

问题描述

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

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.

根据此处

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是在Stateless小部件中的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()中,如果您使用提供程序,则在获取Auth服务时会遇到问题(上下文尚无法通过提供的服务来准备).如果您不使用提供程序,则可以收听initState()中的更改.但是我强烈建议您使用提供程序来分隔您的服务和页面.换句话说,使用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

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

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