Flutter Stream Builder在调用Navigator Pop或Push时触发

时间:2019-07-01 12:04:13

标签: flutter dart firebase-authentication flutter-layout stream-builder

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

根据herehere的理解,是在导航器中弹出/推入页面时,它触发了应用程序的重建,因此流构建器被重新连接,因此它开火。但是,这似乎效率低下,因此,有没有一种方法可以防止在弹出/推送页面时触发流生成器?

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

下面是完整的代码和日志。该代码使用Firebase身份验证作为流生成器。

代码:

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"),
            ),
          ],
        ),
      ),
    );
  }
}

日志,其中第四行表示按下按钮进行导航。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.

3 个答案:

答案 0 :(得分:2)

我花了几个小时弄清楚如何解决此问题。原来AppHomePage需要扩展StatefulWidget而不是StatelessWidget

不知道为什么,但是可以。

答案 1 :(得分:1)

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

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

因此,在大多数教程中,您将看到StreamBuilder是在Stateless小部件中的build方法中创建的。这不是一个有效的解决方案。

请改为使用有状态窗口小部件,并以initState()didChangeDependencies()方法监听您的身份验证更改。

在我们的案例中,不同之处在于,在initState()中,如果您使用提供程序,则在获取Auth服务时会遇到问题(上下文尚无法通过Provided服务准备就绪)。如果您不使用提供程序,则可以收听initState()中的更改。但是我强烈建议您使用提供程序来分隔您的服务和页面。换句话说,使用MVVM模式可以使您的代码具有可伸缩性和可维护性。

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代替我的流

答案 2 :(得分:1)

在 AppHomePage StatelessWidget 中将你的 StreamBuilder 包裹在 Scaffold 小部件下,这样当 Navigator Pop 或 Push 被调用时它不会被触发。