如何在 Navigator.of(context).push(....) 之后临时取消订阅 Stream?
Posted
技术标签:
【中文标题】如何在 Navigator.of(context).push(....) 之后临时取消订阅 Stream?【英文标题】:How to temporarily unsubscribe to Stream after Navigator.of(context).push(....)? 【发布时间】:2020-06-05 02:44:48 【问题描述】:场景:有两个页面。 PhonePage
和 OtpPage
。用户在PhonePage
中输入电话号码并被重定向到OtpPage
以验证发送给他的OTP。
问题:与服务器对话的 API 使用 StreamController.broadcast()
告诉应用程序响应请求。此流由PhonePage
和OtpPage
共享并产生事件。两个页面监听流并根据事件决定做什么。
但是,Navigator.push()
之后,旧页面仍在监听流。因此,当OtpPage
中的用户点击重新发送按钮时,PhonePage
中的Navigator.push
仍会被调用,尽管它不应该调用。
问题 Flutter 必须如何处理这种情况?我试过onDispose()
,但它没有被调用。
如果您也能解释为什么也没有调用 onDispose
,我将不胜感激。
代码:这是重现场景的代码。您可以将其粘贴到您的 IDE 或 DartPad https://dartpad.dev/flutter (注意:当您转到 OtpPage
并在文本字段中添加一些文本时,单击重新发送按钮。注意如何在顶部添加一个新的 OtpPage
小部件导航树。这是不受欢迎的行为)
import 'dart:async';
import 'package:flutter/material.dart';
final fakeApiResponse = StreamController.broadcast();
void main() => runApp(MyApp(),);
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PhoneNumber(),
);
class PhoneNumber extends StatefulWidget
@override
_PhoneNumberState createState() => _PhoneNumberState();
class _PhoneNumberState extends State<PhoneNumber>
StreamSubscription apiEventListner;
@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Enter your phone number'),
RaisedButton(
child: Text('Send OTP'),
onPressed: ()
fakeApiResponse.add('OTP Sent');
,
),
],
),
);
@override
void initState()
super.initState();
apiEventListner = fakeApiResponse.stream.listen((data)
if (data == 'OTP Sent')
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => VerifyOtp(),
),
);
);
@override
void dispose()
super.dispose();
apiEventListner.cancel();
class VerifyOtp extends StatefulWidget
@override
_VerifyOtpState createState() => _VerifyOtpState();
class _VerifyOtpState extends State<VerifyOtp>
StreamSubscription apiEventListner;
@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(
decoration: InputDecoration(hintText: 'Enter OTP Here'),
),
RaisedButton(
child: Text("Verify"),
onPressed: ()
fakeApiResponse.add('OTP Verified');
,
),
RaisedButton(
child: Text("Didn't get the code? Resend OTP"),
onPressed: ()
fakeApiResponse.add('OTP Sent');
,
),
],
),
);
@override
void initState()
super.initState();
apiEventListner = fakeApiResponse.stream.listen((data)
if (data == 'OTP Sent')
// show the dialog
showDialog(
context: context,
builder: (BuildContext context)
return AlertDialog(
title: Text("OTP Resent"),
content: Text("Enter new OTP"),
);
,
);
else if (data == 'OTP Verified')
Navigator.of(context).push(MaterialPageRoute(builder: (context)=>SuccessPage()));
);
@override
void dispose()
super.dispose();
apiEventListner.cancel();
class SuccessPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Text('SUCCESS!'),
),
);
【问题讨论】:
我无权访问 Api 代码,因为它是一个库,因此我无法控制响应事件名称。 Api 只知道一件事:Send Otp 将响应 Otp Sent。它没有重新发送 Otp 的概念。您只需再次调用 Send Otp 即可重新发送它 【参考方案1】:我找到了解决方案。无论如何我都会在这里发布它,以防它可能对其他人有帮助。
诀窍是检测当前活动路由,如果当前小部件不是 Streams 侦听器内的当前路由,则返回。
Flutter 有一个名为 ModalRoute route = ModalRoute.of(context);
的 API,如果当前小部件是当前路由,route.isCurrent
将为真。
然后您必须在两个页面中添加此检查。
最终的工作代码是:
import 'dart:async';
import 'package:flutter/material.dart';
final fakeApiResponse = StreamController.broadcast();
void main() => runApp(
MyApp(),
);
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PhoneNumber(),
);
class PhoneNumber extends StatefulWidget
@override
_PhoneNumberState createState() => _PhoneNumberState();
class _PhoneNumberState extends State<PhoneNumber>
StreamSubscription apiEventListner;
@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Enter your phone number'),
RaisedButton(
child: Text('Send OTP'),
onPressed: ()
fakeApiResponse.add('OTP Sent');
,
),
],
),
);
@override
void initState()
super.initState();
apiEventListner = fakeApiResponse.stream.listen((data)
ModalRoute route = ModalRoute.of(context);
String name = route?.settings?.name;
print("Phone page isCurrent: $route?.isCurrent isFirst: $route?.isFirst active: $route?.isActive $name");
if (route?.isCurrent != null && !route.isCurrent)
return;
if (data == 'OTP Sent')
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => VerifyOtp(),
),
);
);
@override
void dispose()
super.dispose();
apiEventListner.cancel();
class VerifyOtp extends StatefulWidget
@override
_VerifyOtpState createState() => _VerifyOtpState();
class _VerifyOtpState extends State<VerifyOtp>
StreamSubscription apiEventListner;
@override
Widget build(BuildContext context)
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(
decoration: InputDecoration(hintText: 'Enter OTP Here'),
),
RaisedButton(
child: Text("Verify"),
onPressed: ()
fakeApiResponse.add('OTP Verified');
,
),
RaisedButton(
child: Text("Didn't get the code? Resend OTP"),
onPressed: ()
fakeApiResponse.add('OTP Sent');
,
),
],
),
);
@override
void initState()
super.initState();
apiEventListner = fakeApiResponse.stream.listen((data)
ModalRoute route = ModalRoute.of(context);
String name = route?.settings?.name;
print("OTP page isCurrent: $route?.isCurrent isFirst: $route?.isFirst active: $route?.isActive $name");
if (route?.isCurrent != null && !route.isCurrent)
return;
if (data == 'OTP Sent')
// show the dialog
showDialog(
context: context,
builder: (BuildContext context)
return AlertDialog(
title: Text("OTP Resent"),
content: Text("Enter new OTP"),
);
,
);
else if (data == 'OTP Verified')
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SuccessPage()));
);
@override
void dispose()
super.dispose();
apiEventListner.cancel();
class SuccessPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Text('SUCCESS!'),
),
);
【讨论】:
以上是关于如何在 Navigator.of(context).push(....) 之后临时取消订阅 Stream?的主要内容,如果未能解决你的问题,请参考以下文章
Flutter:Navigator.of(context).pop() 返回黑屏
Navigator.of(context, rootNavigator: true).push()中的`rootNavigator`有啥用?
Navigator.pop() - 如何传递 `context` 以供导航器读取 -
在showDialog中Flutter Navigator.of(context).pop(),在ios中关闭整个应用程序
查找已停用小部件的祖先是不安全的 => 使用 Riverpod => 使用 "Navigator.of(context).pushReplacementNamed('/page'