I am trying to use GoRoute, Stream, and Bloc. So, I am implementing Auth Process. So, if the user is not log-in they can't access any page. And, `Stream allows to constantly listen to AuthStatus (Bloc State). But, so how I am not sure, why but I am getting this error
Exception: 'package:go_router/src/go_router_delegate.dart':
Failed assertion: line 299 pos 13: '!redirects.contains(redir)':
redirect loop detected:
I tried to create a dummy app to understand what is exactly going wrong. But, I wasn't able to fig-out what is the reason. Just below I am attaching the code it's just ~200 lines anyone can copy and paste it and then run the command flutter run
and an app error appears in the screen.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
enum Pages {
home,
profile,
settings,
}
class FooStream {
final StreamController<Pages> _controller;
FooStream() : _controller = StreamController.broadcast();
Stream<Pages> get stream {
final listOfPages = [Pages.home, Pages.profile, Pages.settings];
// every 3 seconds loop over enum and emit a new value to the stream
Timer.periodic(const Duration(seconds: 3), (timer) {
final index = timer.tick ~/ 3;
if (index >= listOfPages.length) {
timer.cancel();
} else {
_controller.add(listOfPages[index]);
}
});
return _controller.stream;
}
void update(Pages page) {
_controller.add(page);
}
void close() {
_controller.close();
}
void dispose() {
_controller.close();
}
}
class FooNotifier extends ChangeNotifier {
final ValueNotifier<Pages> _page = ValueNotifier(Pages.home);
ValueNotifier<Pages> get page => _page;
// listen to the stream and whenever it emits a new value, update the page
final FooStream stream;
FooNotifier(this.stream) {
stream.stream.listen((Pages page) {
_page.value = page;
notifyListeners();
});
}
void close() {
notifyListeners();
}
@override
void dispose() {
notifyListeners();
super.dispose();
}
}
// my home page
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
"Home",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
)),
);
}
}
// profile page
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
"Profile",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
)),
);
}
}
// setting page
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
"Setting",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
)),
);
}
}
class FooRoute {
final FooNotifier notifier;
FooRoute(this.notifier);
GoRouter routeTo() {
return GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const MyHomePage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfilePage(),
),
GoRoute(
path: '/setting',
builder: (context, state) => const SettingPage(),
),
],
refreshListenable: notifier,
redirect: safePage,
debugLogDiagnostics: true,
);
}
// on page change, return the new page
String? safePage(GoRouterState state) {
final newPage = notifier.page.value;
// if the new page is the same as the current page, do nothing
if (newPage == Pages.home) {
return '/';
}
if (newPage == Pages.profile) {
return '/profile';
}
if (newPage == Pages.settings) {
return '/settings';
}
return null;
}
}
class FooApp extends StatelessWidget {
FooApp({
Key? key,
}) : super(key: key);
final _router = FooRoute(FooNotifier(FooStream())).routeTo();
// base the
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Pages>(
valueListenable: FooNotifier(FooStream()).page,
builder: (context, value, child) {
return MaterialApp.router(
routerDelegate: _router.routerDelegate,
routeInformationParser: _router.routeInformationParser,
);
},
);
}
}
void main(List<String> args) {
runApp(FooApp());
}
Launching lib\main.dart on Windows in debug mode...
package:bug_test/main.dart:1
Connecting to VM Service at ws://127.0.0.1:63905/cNY-SYgW7KE=/ws
[GoRouter] known full paths for routes:
[GoRouter] => /
[GoRouter] => /profile
[GoRouter] => /setting
[GoRouter] setting initial location /
flutter: ══╡ EXCEPTION CAUGHT BY GOROUTER ╞══════════════════════════════════════════════════════════════════
flutter: The following _Exception was thrown Exception during GoRouter navigation:
flutter: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13:
package:go_router/src/go_router_delegate.dart:299
flutter: '!redirects.contains(redir)': redirect loop detected: / => /
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #2 GoRouterDelegate._getLocRouteMatchesWithRedirects.redirected
package:go_router/src/go_router_delegate.dart:299
flutter: #3 GoRouterDelegate._getLocRouteMatchesWithRedirects
package:go_router/src/go_router_delegate.dart:322
flutter: #4 GoRouterDelegate._go
package:go_router/src/go_router_delegate.dart:245
flutter: #5 new GoRouterDelegate
package:go_router/src/go_router_delegate.dart:58
flutter: #6 new GoRouter
package:go_router/src/go_router.dart:46
flutter: #7 FooRoute.routeTo
package:bug_test/main.dart:122
flutter: #8 new FooApp
package:bug_test/main.dart:169
flutter: #9 main
flutter: #10 _runMain.<anonymous closure> (dart:ui/hooks.dart:132:23)
flutter: #11 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
flutter: (elided 3 frames from class _AssertionError and class _RawReceivePortImpl)
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /
package:go_router/src/go_router_delegate.dart:299
[GoRouter] MaterialApp found
[GoRouter] refreshing /
2
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /profile => /profile
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
[GoRouter] redirecting to /profile
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /profile => /profile
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /settings => /settings
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
[GoRouter] redirecting to /settings
Application finished.
Exited (sigterm)
I would be really grateful if someone can help me. Thank you
I have raised this issue in Flutter's official GitHub Repo here is the link please do check the below link https://github.com/flutter/flutter/issues/104441
I am posting a short answer. If it helps you in anyway, I will describe completely how I navigated around this issue.
Long story short:
The redirect function of go_router for some reason runs twice. For these reason, I have set up my if/else conditions in such a way that it always redirects to my desired destination.
redirect: (state) {
final isLogging = state.location == '/login';
final isLoggingByPhoneNumber =
state.location == '/login/loginUsingPhoneNumber';
final isSigning = state.location == '/signup';
if (!userAuthDao.isLoggedIn()) { //If user is not logged in.
return isLogging
? null
: isSigning
? null
: isLoggingByPhoneNumber
? null
: '/login';
}
final isLoggedIn = state.location == '/';
if (userAuthDao.isLoggedIn() && isLogging) return isLoggedIn ? null : '/';
if (userAuthDao.isLoggedIn() && isSigning) return isLoggedIn ? null : '/';
if (userAuthDao.isLoggedIn() && isLoggingByPhoneNumber) {
return isLoggedIn ? null : '/';
}
return null; //if none of the conditions are triggered, you will be routed to where ever you were going.
},
A much more complex routing logic:
redirect: (state) {
final isLogging = state.location == '/login';
final isInitializing = state.location == '/0';
final isOnboarding = state.location == '/onboarding';
final isGoingProfilePage = state.location == '/profile';
if (!appStateManager.isInitialized) return isInitializing ? null : '/0';
if (appStateManager.isInitialized && !appStateManager.isLoggedIn)
return isLogging ? null : '/login';
if (appStateManager.isLoggedIn && !appStateManager.isOnboardingComplete)
return isOnboarding ? null : '/onboarding';
x = appStateManager.getSelectedTab;
final isGoingHome = state.location == '/home/tab/${x}';
if (appStateManager.isOnboardingComplete &&
!profileManager.didSelectUser &&
!groceryManager.isCreatingNewItem &&
!(groceryManager.selectedIndex != -1))
return isGoingHome
? null
: '/home/tab/${x}';
final isGoingToCreate = state.location == '/home/tab/2/newitem';
if (groceryManager.isCreatingNewItem)
return isGoingToCreate ? null : '/home/tab/${x}/newitem';
final isViewingExistingItem =
state.location == '/home/tab/2/item/${groceryManager.selectedIndex}';
if (groceryManager.selectedIndex != -1)
return isViewingExistingItem
? null
: '/home/tab/2/item/${groceryManager.selectedIndex}';
if (profileManager.didSelectUser)
return isGoingProfilePage ? null : '/profile';
return null;
},