调用 Navigator Pop 或 Push 时触发 Flutter Stream Builder
Posted
技术标签:
【中文标题】调用 Navigator Pop 或 Push 时触发 Flutter Stream Builder【英文标题】:Flutter Stream Builder Triggered when Navigator Pop or Push is Called 【发布时间】:2019-11-12 02:13:25 【问题描述】:我在应用的主页/根页面中有一个流生成器。每当我在其他地方进行页面导航时都会触发此流构建器,这与流本身无关。
根据here 和here,我的理解是当页面在导航器中弹出/推送时,它会触发应用程序的重建,因此流构建器会重新连接并触发。但是这似乎效率低下,那么有没有办法防止在弹出/推送页面时触发流构建器?
此外,根据日志,当我推送页面时,页面首先构建并显示,然后流构建器被触发。然而,流构建器的小部件/页面根本不显示,即使日志/调试器清楚地显示流构建器的小部件已返回。它去哪儿了?它在 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"),
),
],
),
),
);
日志,其中第 4 行表示按下按钮执行 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.
【问题讨论】:
【参考方案1】:我可以确认每次我们在应用程序中导航时都会调用 StreamBuilder 中的 build 方法,这效率不高,因为它应该取消其侦听器,创建一个新侦听器并重建整个小部件。
如果您的应用侦听身份验证状态以便在身份验证状态更改(加载/登录/主页)时显示适当的屏幕,您可能会遇到该问题
所以在大多数教程中,您会看到 StreamBuilder 是在无状态小部件的 build 方法中创建的。这不是一个有效的解决方案。
改为使用 Stateful 小部件并在 initState()
或 didChangeDependencies()
方法中监听您的身份验证更改。
我们案例的不同之处在于,在initState()
中,如果您使用 Provider,您在获取 Auth 服务时会遇到问题(上下文尚未准备好使用 Provided 服务)。如果您不使用 Provider,您可以收听 initState()
中的更改。但我强烈建议使用 Provider 来分离您的服务和页面。换句话说,使用 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
【讨论】:
感谢您的详尽解释和有趣的方法。 感谢您提供此信息!你有一个在init中监听事件的例子吗?我使用全局变量而不是 Provider……全局变量似乎比 Provider 容易得多……【参考方案2】:我花了几个小时想办法解决这个问题。原来AppHomePage
需要扩展StatefulWidget
而不是StatelessWidget
。
不知道为什么,但它确实有效。
【讨论】:
我也花了几个小时。当我回到家时,我会尝试这个解决方案。使用有状态小部件非常奇怪。如果有人可以解释或链接到一些很棒的资源。【参考方案3】:在 AppHomePage StatelessWidget 中将 StreamBuilder 包裹在 Scaffold 小部件下,这样当 Navigator Pop 或 Push 被调用时它就不会被触发。
【讨论】:
以上是关于调用 Navigator Pop 或 Push 时触发 Flutter Stream Builder的主要内容,如果未能解决你的问题,请参考以下文章
是否可以使用 navigator.pop 刷新或重新加载页面...比如第 1(nav.push=>2)页面到第 2 页,然后从第 2(nav.pop,值)返回到第 1 页?
Flutter 图像在 navigator pop 上再次下载
使用 page_transition 更改 Navigator.pop() 动画持续时间