I recently upgraded to Flutter to 3.13 from 3.3 and am now receiving these Exceptions back to back upon hot reloads.
I have included both exceptions as they may be linked and am uncertain if they are seperate problems.
How can I find the source of the problem? The exceptions don't land on any of my code, besides my root MaterialApp, which doesn't explain/lead to much.
Please request any additional information that might be useful and I will provide it.
Debug console:
════════ Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building DefaultSelectionStyle:
RangeError (startIndex): Invalid value: Only valid value is 0: 1
The relevant error-causing widget was
MaterialApp
When the exception was thrown, this was the stack
#0 RangeError.checkValueInInterval (dart:core/errors.dart:313:7)
#1 _StringBase.replaceFirst (dart:core-patch/string_patch.dart:591:16)
...
#259 RendererBinding._handlePersistentFrameCallback
binding.dart:360
#260 SchedulerBinding._invokeFrameCallback
binding.dart:1297
#261 SchedulerBinding.handleDrawFrame
binding.dart:1227
#262 SchedulerBinding.scheduleWarmUpFrame.<anonymous closure>
binding.dart:952
#266 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)
(elided 3 frames from class _Timer and dart:async-patch)
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by widgets library ═══════════════════════════════════
RangeError (startIndex): Invalid value: Only valid value is 0: 1
The relevant error-causing widget was
MaterialApp
main.dart:71
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by scheduler library ═════════════════════════════════
buildScope missed some dirty elements.
The list of dirty elements at the end of the buildScope call was
App
StreamAuthScope
MaterialApp
state: _MaterialAppState#1c346
MyRoot
dependencies: [GoRouterStateRegistryScope, StreamAuthScope, _ModalScopeStatus]
state: _MyRootState#4d14d
MyTabBarScaffold
state: _MyTabBarScaffoldState#a8f80
Material
type: canvas
dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#20b1a]]
state: _MaterialState#ee00a
...
════════════════════════════════════════════════════════════════════════════════
Restarted application in 1,785ms.
main.dart. Debug console points to child: MaterialApp.router
as the offending widget.
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return StreamAuthScope(
child: MaterialApp.router(
title: "My App",
debugShowCheckedModeBanner: false,
routerConfig: MyRouter().router,
),
);
}
}
MyRouter()
:
final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
class MyRouter {
static final MyRouter _singleton = MyRouter._internal();
factory MyRouter() {
return _singleton;
}
MyRouter._internal() {
// init
MyHelper().rootNavigatorKey = _rootNavigatorKey;
}
static String shortIdKey = 'shortId';
static String tabRouteNameKey = 'tabRouteName';
final router = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: MyRouter.loadingSplash.path,
routes: [
MyRouter.loadingSplash,
MyRouter.signUp,
MyRouter.myRoot,
],
);
static const String loadingSplashRouteName = 'loadingSplash';
static GoRoute get loadingSplash {
return GoRoute(
name: loadingSplashRouteName,
path: '/',
builder: (context, state) => LoadingSplash(),
redirect: (context, state) {
String? user = _userRedirect(context);
String? noUser = _noUserRedirect(context);
String? redirect = user;
if (noUser != null) {
redirect = noUser;
}
return redirect;
},
);
}
// REDIRECTS
//
static String? _noUserRedirect(BuildContext context) {
final StreamAuth streamAuth = StreamAuthScope.of(context);
if (streamAuth.hasReceivedInitialData && !ProfilesManager.hasLoggedInUser) {
return MyRouter.signUp.path;
} else {
return null;
}
}
static String? _userRedirect(BuildContext context) {
final StreamAuth streamAuth = StreamAuthScope.of(context);
if (streamAuth.hasReceivedInitialData && ProfilesManager.hasLoggedInUser) {
return '/${ProfilesManager().currentUserHelper!.profile.shortId}/${MyRouter.tcTabRouteName}}' //MyRouter.tRoot.path;
} else {
return null;
}
}
static const String signUpRouteName = 'signUp';
static GoRoute get signUp {
return GoRoute(
name: signUpRouteName,
path: '/sign-up',
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: kIsWeb ? MySignUp() : Login(),
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
child,
);
},
redirect: (context, state) {
String? user = _userRedirect(context);
String? redirect = user;
return redirect;
},
);
}
static String tcTabRouteName = 'tc';
static String twTabRouteName = 'tw';
static String teTabRouteName = 'te';
static String tpTabRouteName = 'tp';
static String tmTabRouteName = 'tm';
static const String myRootRouteName = 'mr';
static GoRoute get myRoot {
return GoRoute(
name: myRootRouteName,
path: '/:$shortIdKey/:$tabRouteNameKey',
pageBuilder: (context, state) {
String shortId = state.pathParameters[shortIdKey]!;
String tabRouteName = state.pathParameters[tabRouteNameKey]!;
return CustomTransitionPage(
key: state.pageKey,
child: MyRoot(
shortId: shortId,
tabRouteName: tabRouteName,
tabs: [
PTTabBarItem(
title: 'Tab One',
isSelected: tabRouteName == tcTabRouteName,
tabRouteName: tcTabRouteName,
child: TabOneWidget(
shortId: shortId,
),
),
PTTabBarItem(
title: 'Tab Two',
isSelected: tabRouteName == twTabRouteName,
tabRouteName: twTabRouteName,
child: TabTwoWidget(
shortId: shortId,
),
),
PTTabBarItem(
title: 'Tab Three',
isSelected: tabRouteName == teTabRouteName,
tabRouteName: teTabRouteName,
child: TabThreeWidget(
shortId: shortId,
),
),
PTTabBarItem(
title: 'Tab Four',
isSelected: tabRouteName == tpTabRouteName,
tabRouteName: tpTabRouteName,
child: TabFourWidget(),
),
],
),
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
child,
);
},
redirect: (context, state) {
String? noUser = _noUserRedirect(context);
String? redirect = noUser;
return redirect;
},
);
}
}
MyTabBarScaffold
:
class MyTabBarScaffold extends StatefulWidget {
final Widget child;
final List<MyTabBarItem> tabs;
final MyThemeData? themeData;
final int selectedIndex;
final Function(int index) onTabSelected;
const MyTabBarScaffold({
required this.child,
required this.tabs,
this.themeData,
this.selectedIndex = 0,
required this.onTabSelected,
Key? key,
}) : super(key: key);
@override
State<MyTabBarScaffold> createState() => _MyTabBarScaffoldState();
}
class _MyTabBarScaffoldState extends State<MyTabBarScaffold> {
void _onItemTapped(int index, BuildContext context) {
widget.onTabSelected(index);
}
@override
Widget build(BuildContext context) {
return Material(
child: KeyboardVisibilityBuilder(
builder: (context, isKeyboardVisible) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned(
top: 0,
left: 0,
right: 0,
bottom: isKeyboardVisible
? 0
: MyTheme.tabBarHeight(
context: context,
includeSystemPadding: true,
),
child: widget.child,
),
Positioned(
bottom: isKeyboardVisible
? (-MyTheme.tabBarHeight(
context: context,
includeSystemPadding: true,
))
: 0,
left: 0,
right: 0,
child: _buildTabBar(),
),
],
);
},
),
);
}
Widget _buildTabBar() {
return MyTabBar(
themeData: widget.themeData ?? MyTheme().themeData,
includeSystemPadding: true,
tabs: widget.tabs,
selectedIndex: widget.selectedIndex,
onIndexChanged: (index) => _onItemTapped(index, context),
);
}
}
While the exceptions/debug console offered no clear explanation, it is caused by a
go_router
package update.go_router
8.0 introduced this breaking change:The solution, without making URL/routing changes, is to set the following variable at the root of your app: