场景:如何在Navigator.of(context).push(...)后暂时取消订阅? : 有两页。PhonePage
和:有两页。OtpPage
. 用户输入电话号码 PhonePage
并被重定向到 OtpPage
来验证发给他的OTP。
问题: 与服务器对话的API使用的是 StreamController.broadcast()
来告诉应用程序该请求的响应。这个流由两个 PhonePage
和 OtpPage
并产生事件。这两个页面会监听流,并根据事件决定做什么。
然而,在 Navigator.push()
,旧页面仍在监听流。因此,当用户在 OtpPage
点击重新发送按钮。Navigator.push
在 PhonePage
虽然不应该被调用,但仍然被调用。
疑问 Flutter有什么处理这种情况?我试过 onDispose()
但它不被调用。如果你能解释为什么我也会感激 onDispose
也不被调用。
编码这是重现该场景的代码。你可以把它粘贴在你的IDE或DartPad上。https:/dartpad.devflutter (注:当你进入 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!'),
),
);
}
}
我找到了解决方案。我打算在这里发布任何方式,以防止它可能对其他人有帮助。
诀窍是检测当前的活动路由,如果当前的widget不是当前的路由,则返回流的监听器里面。
Flutter有一个API叫做 ModalRoute route = ModalRoute.of(context);
然后 route.isCurrent
将为真,如果当前的widget是当前的路由。
然后你必须在 两者 页。
最后的工作守则将是:
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!'),
),
);
}
}