go_router_back_handler
A robust, production-ready back button handler for GoRouter that works on all routes, including root routes. Solves the PopScope limitation in GoRouter v13+ with native Android integration.
🎯 Problem Solved
Starting with GoRouter v13+, PopScope doesn't work on root routes (see Flutter issue #140869). This package provides a reliable solution using native Android MethodChannel integration.
✨ Features
- ✅ Works on ALL routes - Including root routes where PopScope fails
- ✅ Smart route detection - Automatically finds parent routes
- ✅ Customizable exit confirmation - Beautiful default dialog with full customization
- ✅ Sequential flow support - Perfect for registration/wizard flows
- ✅ Dynamic route handling - Works with parameterized routes like
/user/:id - ✅ Zero configuration - Works out of the box with sensible defaults
- ✅ Production tested - Battle-tested in real-world applications
- ✅ Type-safe API - Full Dart type safety
- ✅ Detailed logging - Debug mode for troubleshooting
📦 Installation
Add to your pubspec.yaml:
dependencies:
go_router_back_handler: ^1.0.0
Run:
flutter pub get
🚀 Quick Start
1. Update MainActivity.kt
Replace your MainActivity.kt with:
package com.yourcompany.yourapp
import io.flutter.embedding.android.FlutterActivity
import android.os.Bundle
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
flutterEngine?.dartExecutor?.binaryMessenger?.let { messenger ->
val channel = MethodChannel(messenger, "com.gorouter.back_handler/back_button")
channel.invokeMethod("onBackPressed", null, object : MethodChannel.Result {
override fun success(result: Any?) {
// Flutter handled it
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
this@MainActivity.finish()
}
override fun notImplemented() {
this@MainActivity.finish()
}
})
}
}
}
2. Initialize in main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:go_router_back_handler/go_router_back_handler.dart';
final rootNavigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
// Initialize the back button handler
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
routeHierarchy: {
'/profile': '/home',
'/settings': '/home',
'/details': '/list',
},
rootRoutes: ['/', '/login', '/home'],
);
}
@override
void dispose() {
GoRouterBackHandler.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: GoRouter(
navigatorKey: rootNavigatorKey,
routes: [
// Your routes here
],
),
);
}
}
3. That's it! 🎉
Your back button now works intelligently on all routes.
📖 Usage Examples
Basic Setup
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
);
Custom Route Hierarchy
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
routeHierarchy: {
// Authentication flow
'/login/otp': '/login',
'/register/step2': '/register/step1',
'/register/step3': '/register/step2',
// App navigation
'/profile': '/home',
'/settings': '/home',
'/details': '/list',
// Booking flow
'/checkout': '/cart',
'/payment': '/checkout',
'/confirmation': '/payment',
},
rootRoutes: ['/', '/login', '/home'],
moduleHomes: {
'/home': '/dashboard',
'/profile': '/dashboard',
},
);
Custom Exit Message
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
exitMessage: 'Tap back again to exit',
exitDialogTitle: 'Exit App?',
exitDialogMessage: 'Are you sure you want to exit?',
);
Enable Debug Logging
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
debugMode: true, // Shows detailed logs
);
🎨 Customization
Custom Exit Dialog
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
customExitDialog: (context) async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Custom Exit Dialog'),
content: Text('Do you want to exit?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('Exit'),
),
],
),
);
},
);
Custom Toast Widget
GoRouterBackHandler.initialize(
navigatorKey: rootNavigatorKey,
customToast: (context, message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
);
🔧 How It Works
- Native Integration: Intercepts back button at Android native level
- MethodChannel: Communicates with Flutter via platform channel
- Smart Detection: Analyzes current route and finds appropriate parent
- Fallback Strategy:
- Check if root route → Show exit confirmation
- Find parent in hierarchy → Navigate to parent
- Try GoRouter's
canPop()→ Pop - Fallback → Navigate to default route
📱 Behavior
Root Routes
On root routes (/, /login, /home):
- First back press: Shows toast "Press back again to exit"
- Second back press (within 2s): Shows exit confirmation dialog
- After 2s: Timer resets
Child Routes
On child routes:
- Navigates to parent route defined in hierarchy
- Falls back to GoRouter's pop if no parent defined
Module Homes
On module home routes:
- Navigates to their parent module (e.g., /profile → /dashboard)
🐛 Troubleshooting
Back button not working?
- Hot restart required: Native code changes need full restart
- Check MainActivity.kt: Ensure it's properly configured
- Enable debug mode: Set
debugMode: trueto see logs - Verify navigator key: Ensure you're passing the correct key
Exit dialog not showing?
- Ensure the route is in
rootRouteslist - Check if context has Scaffold ancestor
- Try custom exit dialog for more control
🤝 Contributing
Contributions are welcome! Please read our contributing guide.
📄 License
MIT License - see LICENSE file for details.
👨💻 Author
Md. Jehad (Jehadur Rahman Emran)
Full Stack Developer & System Architect | Cloud Connect AI
- 💼 GitHub: @JehadurRE
- 📧 Email: emran.jehadur@gmail.com
- 🔗 LinkedIn: linkedin.com/in/jehadurre
"Building digital solutions that empower developers through thoughtful design and robust engineering."
🙏 Acknowledgments
- Inspired by the need to solve GoRouter's PopScope limitation
- Built with ❤️ for the Flutter community
📊 Stats
- ⭐ Star this repo if you find it helpful!
- 🐛 Report issues on GitHub
- 💡 Feature requests welcome!
Libraries
- go_router_back_handler
- A robust back button handler for GoRouter that works on all routes.