New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug?] in didPopNext and didPop call order #26902
Comments
Please add the output of |
|
I have exactly the same issue. @LiviuStefanLucaTAB Have you found a workaround? |
Issue replicable on latest stable (1.20.2).
|
Hey guys, have the same issue, have you found any workaround? Thanks! |
Hi @antonkrasov I did make a custom navigator based on Navigator 2.0 with events like onTop, willShow, isShown and mayPop. It was the only way I could get this working reliable and it is a navigation pattern that I implemented on some other platforms. Working with the default didPop and didPopNext got way too complex for me and felt like mixing business logic with UI logic. You can find it here: That version is working, but not production-ready. It's more like an early demo/proof of concept. |
I see the same logs as reported above on the latest versions of flutter.
updated sampleimport 'package:flutter/material.dart';
void main() => runApp(const MyApp());
final routeObserver = RouteObserver<PageRoute>();
ValueNotifier<bool> isFeatureFlagEnabled = ValueNotifier(false);
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [routeObserver],
title: 'Flutter Demo',
theme: ThemeData(
// is not restarted.
primarySwatch: Colors.blue,
),
home: const Page1(),
);
}
}
// Feature Flag ENABLED
class Page3 extends StatelessWidget {
const Page3({super.key});
@override
Widget build(BuildContext context) {
return FeatureFlagWidget(
child: Scaffold(
appBar: AppBar(
leading: const BackButton(),
title: const Text('Page 3'),
centerTitle: true,
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: isFeatureFlagEnabled,
builder: (BuildContext context, Widget? child) {
return Text('Feature flag is enabled is '
'${isFeatureFlagEnabled.value.toString().toUpperCase()}');
},
),
],
),
)),
),
);
}
}
// Feature Flag NOT ENABLED
class Page1 extends StatelessWidget {
const Page1({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: const BackButton(),
title: const Text('Page 1'),
centerTitle: true,
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: isFeatureFlagEnabled,
builder: (BuildContext context, Widget? child) {
return Text('Feature flag is enabled is '
'${isFeatureFlagEnabled.value.toString().toUpperCase()}');
},
),
ElevatedButton(
onPressed: () {
_pushMaterialPageRoute(context, const Page2());
},
child: const Text('Go to Page 2'),
),
],
),
)),
);
}
}
// Feature Flag ENABLED
class Page2 extends StatelessWidget {
const Page2({super.key});
@override
Widget build(BuildContext context) {
print("hello");
return FeatureFlagWidget(
child: Scaffold(
appBar: AppBar(
leading: const BackButton(),
title: const Text('Page 2'),
centerTitle: true,
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: isFeatureFlagEnabled,
builder: (BuildContext context, Widget? child) {
return Text('Feature flag is enabled is '
'${isFeatureFlagEnabled.value.toString().toUpperCase()}');
},
),
ElevatedButton(
onPressed: () {
_pushMaterialPageRoute(context, const Page3());
},
child: const Text('Go to Page 3'),
),
],
),
)),
),
);
}
}
class FeatureFlagWidget extends StatefulWidget {
const FeatureFlagWidget({super.key, required this.child});
final Widget child;
@override
State<FeatureFlagWidget> createState() => _FeatureFlagWidgetState();
}
class _FeatureFlagWidgetState extends State<FeatureFlagWidget> with RouteAware {
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
}
@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
void didPush() {
print("didPush");
// To avoid updating while widget is building
Future.microtask(() {
isFeatureFlagEnabled.value = true;
});
}
@override
void didPopNext() {
print("didPopNext");
isFeatureFlagEnabled.value = true;
}
@override
void didPop() {
print("didPop");
isFeatureFlagEnabled.value = false;
}
@override
void didPushNext() {
print("didPushNext");
isFeatureFlagEnabled.value = false;
}
@override
Widget build(BuildContext context) => widget.child;
}
void _pushMaterialPageRoute(BuildContext context, Widget child) =>
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (BuildContext context) => child));
flutter doctor -v
|
Came across this problem too. My workaround is to create my own RouteObserver class - I copy its code from original RouteObserver and change didPop so that subscribers loop is before the previousSubscribers loop:
Then use MyRouteObserver instead of RouteObserver. flutter: hello |
Hi,
The following code is the logic from
RouteObserver
which calls anyRouteAware
widgets when a route is popped.In my app I have a feature flag that I turn on/off based on the page that I'm showing, and rely on the
RouteAware
callbacks. I wrap all the pages where I want to enable the feature flag in aRouteAware
widget, which turns the global flag toTRUE
ifdidPush(starting the page)
/didPopNext(coming back from the next page)
, orFALSE
ifdidPop(closing the page)
/didPushNext(going to the next page)
.The problem comes when I have two sequential pages that turn ON the feature flag and the second one navigates back to the first one, because the logic calls
previousRouteSubscribers
first, so,didPopNext
on page A is called first, anddidPop
on page B is called second, which turns my flagTRUE
and thenFALSE
, which is not what I want. My first instinct would be that you first want to call the callbacks registered by page B because we are disposing of it, and want to clean it first, and then the callbacks for page A, because we are going to it. Is this the intended order, or should there be a mechanism to chose what order in which you want the callback fired, or should I not rely on this api for what I'm trying to achieve?Minimal reproducible example:
When you run the example, see the when you go forward Page 1 -> Page 2 -> Page 3, feature flag is enabled on Page 2 and Page 3, but when you come back from Page 3 to Page 2, the flag is false. That is because
didPopNext(of Page 2)
got called beforedidPop(of Page 3)
.Thanks!
The text was updated successfully, but these errors were encountered: