How to Fix : Flutter [go_router] PopScope Issue on Android: A Step-by-Step Guide
As a Flutter developer, keeping up with the latest updates and best practices is essential for maintaining and improving your app.Recently, I have faced an issue while migrating one of my client’s Flutter app (medium-level) into the latest version. While migrating the app,the problem occured: the PopScope did not recognize the back button callback on Android devices(Of cause due to go_router package). After some research[#138737, #138525], I came up with the following two potential solutions. Lets get started.
Potential Solutions
Solution 1: Downgrade go_router
and Adjust Android Settings
The easiest way to resolve this issue is to downgrade the go_router package to version 12.1.3 (tested on several projects). It didn’t have issues with the back button callback in this version. Also, don’t forget to edit the Android manifest file to make it compatible.
<application
android:label="Your_App_Name"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:enableOnBackInvokedCallback="false"
>
Then downgrade the go_router in pubspec.yaml.
go_router: ^12.1.3
//for some users [go_router: 13.2.2] worked but didnt work for me
Solution 2: Intercepting the Back Button
The second and, in my opinion, elegant solution is to use a package that intercepts the back button. I mean, just considering all the advantages that this method has against downgrading the go_router package, mainly you don’t need to downgrade your go_router package(Also didn’t mess with your whole project routes considering changing versions).
Implementation Steps
- Add the back_button_interceptor package to your pubspec.yaml.
dependencies:
back_button_interceptor: ^7.0.3
2. Register the interceptor.
//Example from the doc
@override
void initState() {
super.initState();
BackButtonInterceptor.add(myInterceptor);
}
@override
void dispose() {
//dont forget to dispose
BackButtonInterceptor.remove(myInterceptor);
super.dispose();
}
bool myInterceptor(bool stopDefaultButtonEvent, RouteInfo info) {
print("BACK BUTTON!"); // Do some stuff.
return true;
}
And thats it✅ and Nothing more. Do whatever you like inside myInterceptor function , if the function returned true
, the default button process (usually popping a Route) will not be fired.
📝 Example UseCase
In my case, above example introduced a small bug due to the stack navigation structure of my app. I registered the interceptor in the first page of the stack(bottom page in the stack). So, when adding new routes to the stack and navigating to a top page in the stack and hitting the back button, it did not pop the current screen(top page in the stack), but an AlertBox asking for exit confirmation — behavior intended for the first page.
To Fix this, let’s make sure the interceptor is going to run only in case the current route corresponds to a route on which the interceptor was created. Here, info.ifRouteChanged() method will help us ensure the fix.
- Modify the interceptor function to check the current route.
bool myInterceptor(bool stopDefaultButtonEvent, RouteInfo info) {
//if the current page is not the page that the interceptor was created,
//then pop the screen
if (info.ifRouteChanged(context)) return false;
showDialog<CustomExitConfirmDialogBox>(
context: context,
builder: (ctx) =>CustomExitConfirmDialogBox(),
);
return true;
}
//Don't forget to pass the context to the Interceptor,unless it doesnt work
@override
void initState() {
super.initState();
BackButtonInterceptor.add(myInterceptor, context: context);
}
With this adjustment, the interceptor function only executes its custom logic if the current route hasn’t changed from when the interceptor was registered. This ensures that the back button behavior works correctly across different pages in the stack, preventing unintended actions like showing the exit confirmation on top pages.
I hope this fixℹ️ has helped you , drop me a few claps 👏 👏 👏 … if it does! hahaha! Happy Coding! 👨💻
Contact me via LinkedIn