No GoRouter found in context in widget tests - flutter

I'm trying to mock go_router in my widget tests but keep getting this error:
══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No GoRouter found in context
'package:go_router/src/router.dart':
Failed assertion: line 300 pos 12: 'inherited != null'
When the exception was thrown, this was the stack:
#2 GoRouter.of (package:go_router/src/router.dart:300:12)
#3 GoRouterHelper.pushNamed (package:go_router/src/misc/extensions.dart:50:16)
#4 _ProfileEditWidgetState._getSubmitButton.<anonymous closure>.<anonymous closure> (package:app/widgets/profile/profile_edit_widget.dart:236:33)
I'm using go_router 6.0.0
Here's what I have in my test:
final router = MockGoRouter();
when(router.pushNamed(any)).thenReturn(null);
await tester.pumpWidget(
MultiProvider(
providers: [
BlocProvider<ProfileBloc>(create: (context) => profileBloc),
],
child: MaterialApp(
locale: const Locale("en"),
home: InheritedGoRouter(
goRouter: router,
child: ProfileEditWidget(),
),
),
),
);
await tester.pump();
await tester.tap(find.byKey(const Key('btnSubmit')));
await tester.pump();
In my profile edit widget in the submit button on pressed this is what I have:
context.pushNamed(
VERIFY_PHONE,
queryParams: {
'updatedPhone': phone
},
)
There also seems to be no documentation on go to handle testing with go router

Try to change the MaterialApp to MaterialApp.router
And use GoRouter() instead of MockGoRouter()
import 'package:go_router/go_router.dart';
// GoRouter configuration
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
),
],
);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}

Related

Showing Dialog with Auto_Route and BlocListener

I'm developing an app that diplays an alert every time the user turns off the device's Wifi and Mobile Data. I'm also using Auto_Route for navigation.
Here's the code so far:
class AppWidget extends StatelessWidget {
AppWidget({super.key});
final GlobalKey<ScaffoldMessengerState> messengerKey = GlobalKey<ScaffoldMessengerState>();
#override
Widget build(BuildContext context) {
return RepositoryProvider(
create: (_) => getIt<CPlusNetworkRepository>(),
child: BlocProvider(
create: (_) => getIt<ConnectionStatusBloc>()
..add(const StreamSubsricptionRequested()),
child: BlocListener<ConnectionStatusBloc, ConnectionStatusState>(
listener: (context, state) {
state.map(
connected: (_) {},
disconnected: (_) async {
WidgetsBinding.instance.addPostFrameCallback((_) async{
await showCupertinoDialog(
context: context,
builder: (context) => const CupertinoAlertDialog(
title: Text('Alert'),
content: Text('Please switch on Wifi/Mobile Data'),
),
);
});
},
unknown: (_) {},
);
},
child: MaterialApp.router(
scaffoldMessengerKey: messengerKey,
routeInformationParser: appRouter.defaultRouteParser(),
routerDelegate: appRouter.delegate(),
),
),
),
);
}
}
I'm getting this error:
E/flutter ( 9910): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
E/flutter ( 9910): The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
I know there's somethig inherently wrong with what I'm trying to do but I can't seem to find the correct way to approach this.
Any suggestions?
Thanks in advance.
You're trying to show a Dialog, which requires a Navigator to be placed anywhere before it in the widget tree.
A solution would be to place the listener below your Material, so it has Navigator to find up in his BuildContext

Index out of range on Flutter Web

Everything is working fine with me on iOS and Android but once I run my app on the web (Chrome) I got this error from the widget LocalizedApp
════════ Exception caught by widgets library ═══════════════════════════════════
The following IndexError was thrown building MyApp(dirty):
RangeError (index): Index out of range: no indices are valid: 0
The relevant error-causing widget was
MyApp
lib/main.dart:54
When the exception was thrown, this was the stack
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49 throw_
dart-sdk/lib/_internal/js_dev_runtime/private/js_array.dart 581:7 _get]
packages/localize_and_translate/src/main.dart 192:51 get locale
packages/UnitedPalestine/main.dart 85:28 build
packages/flutter/src/widgets/framework.dart 4569:28 build
...
Any extra steps I need to do to make it works on the web?
Code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
//await Firebase.initializeApp();
// ignore: unused_local_variable
await translator.init(
localeDefault: LocalizationDefaultType.device,
languagesList: <String>['ar', 'en'],
assetsDirectory: 'assets/language/',
apiKeyGoogle: '<Key>',
);
}
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
//runApp(MyApp());
runApp(
LocalizedApp(
child: MyApp(),
),
);
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
StreamProvider(
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: SplashScreen.routName,
routes: {
SplashScreen.routName: (ctx) => SplashScreen(),
SigninScreen.routName: (ctx) => SigninScreen(),
SignupScreen.routName: (ctx) => SignupScreen(),
OTPScreen.routName: (ctx) => OTPScreen(),
},
localizationsDelegates: translator.delegates,
locale: translator.locale,
supportedLocales: translator.locals(),
),
);
}
}
That's my main.dart code works perfect on iOS and Android but won't work on the web

Could not use Provider.of in child widgets

I have my main() like this with MultiProvider wrapped with LocalizedApp for localization:
void main() async {
setupLocator();
var delegate = await LocalizationDelegate.create(
fallbackLocale: 'fa',
supportedLocales: ['fa'],
);
FluroRouter.setupRouter();
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(debug: true);
runApp(
LocalizedApp(
delegate,
MultiProvider(
providers: [
StreamProvider<ConnectionStatus>(
create: (context) =>
ConnectivityService().connectivityController.stream,
initialData: ConnectionStatus.offline,
),
ChangeNotifierProvider<AppState>(
create: (BuildContext context) => AppState(),
),
],
child: MyApp(),
),
),
);
}
and MyApp class is as follows again wrapped with LocalizationProvider:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appstate = Provider.of<AppState>(context);
var localizationDelegate = LocalizedApp.of(context).delegate;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: appstate.statusBarColor,
));
return LocalizationProvider(
state: LocalizationProvider.of(context).state,
child: GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
localizationDelegate
],
supportedLocales: localizationDelegate.supportedLocales,
locale: localizationDelegate.currentLocale,
theme: appstate.currentTheme,
initialRoute: 'landing',
onGenerateRoute: FluroRouter.router.generator,
),
),
);
}
}
but even in the initial route which is 'landing' when I try to use a Provider.of<AppState>(context) it throws this error:
Error: Could not find the correct Provider<AppState> above this Landing Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that Landing is under your MultiProvider/Provider<AppState>.
This usually happen when you are creating a provider and trying to read it immediatly.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using `builder` like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builer: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
I don't know what I'm doing wrong here!
also I'm using Fluro v.1.5.1 for navigation.

if the home property is specified the routes table cannot include an entry for /

Getting this error with this code:
void main() => runApp(RouteTestApp());
class RouteTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: FirstScreen(),
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
}
}
The following assertion was thrown building MaterialApp(dirty, state: _MaterialAppState#a959e):
I/flutter (24918): If the home property is specified, the routes table cannot include an entry for "/", since it would
I/flutter (24918): be redundant.
I/flutter (24918): 'package:flutter/src/widgets/app.dart':
I/flutter (24918): Failed assertion: line 172 pos 10: 'home == null ||
I/flutter (24918): !routes.containsKey(Navigator.defaultRouteName)'
The solution is to remove the home property, since it can cause problems if you add the routes property.
class RouteTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
}
}
If you set none as '/' code bellow will help you when test widgets with navigation, and still work.
final routes = <String, WidgetBuilder>{
'/one': (BuildContext context) => PageOne(),
'/two': (BuildContext context) => PageTwo(),
...
};
runApp(MaterialApp(initialRoute: '/one', routes: appRoutes));

BlocProvider.of() called with a context that does not contain a Bloc of type Bloc<dynamic,dynamic>

Error: I/flutter ( 5919): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════ I/flutter
( 5919): The following assertion was thrown building Builder:
I/flutter ( 5919): BlocProvider.of() called with a context
that does not contain a Bloc of type Bloc. I/flutter ( 5919): No ancestor could be found
starting from the context that was passed to I/flutter ( 5919):
BlocProvider.of>(). I/flutter ( 5919):
This can happen if the context you used comes from a widget above the
BlocProvider. I/flutter ( 5919): The context used was:
BlocBuilder, dynamic>(dirty, state: I/flutter (
5919): _BlocBuilderBaseState,
dynamic>#55a7d(lifecycle state: created)) I/flutter ( 5919): The
relevant error-causing widget was: I/flutter ( 5919): MaterialApp
/lib/main.dart:35:12
Here's my main
void main() {
final StorageRepository storageRepository = StorageRepository();
final AuthenticationRepository authenticationRepository =
AuthenticationRepository();
runApp(BlocProvider<AuthenticationBloc>(
create: (_) => AuthenticationBloc(
authenticationRepository: authenticationRepository,
storageRepository: storageRepository),
child: MyApp()));
}
MaterialApp Widget
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.deepPurple),
home: BlocBuilder(
builder: (context, state) {
print(state);
if (state is Authenticated) {
return MainPage();
} else if (state is Unauthenticated) {
return LoginPage();
} else if (state is Uninitialized) {
return SplashScreen();
}
return Container();
},
),
You forget to give the Bloc and State type to the BlocBuilder Widget
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.deepPurple),
/// You need to specify the type here,
/// that's why you got error Bloc<dynamic, dynamic>
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
print(state);
if (state is Authenticated) {
return MainPage();
} else if (state is Unauthenticated) {
return LoginPage();
} else if (state is Uninitialized) {
return SplashScreen();
}
return Container();
},
),
As the error, itself suggest BlocProvider not accessing the right context to use the bloc
MultiBlocProvider provides the ability to add multiple providers which then can get the right context access as MultiBlocProvider converts the BlocProvider list into a tree of nested
BlocProvider widgets.
MultiBlocProvider(
providers: [
BlocProvider<YourBloc>(
create: (BuildContext context) =>)
],
child: MaterialApp(
home: BlocBuilder<YourBloc, YourState>(