Navigator: cannot find a generator/route for pushNamed - flutter

I am trying to change screens using the value of a string inside a json object but I get the following error in the console and it does not change my screen:
Could not find a generator for route RouteSettings ("alert", null) in the _WidgetsAppState.
Generators for routes are searched for in the following order:
For the "/" route, the "home" property, if non-null, is used.
Otherwise, the "routes" table is used, if it has an entry for the route.
Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.
When the exception was thrown, this was the stack
This is my Home Page, the method is working until Navigator.pushNamed(context, opt['ruta']);:
List<Widget> _listItems(List<dynamic> data, BuildContext context) {
final List<Widget> options = [];
data.forEach((opt){
final widgetTemp = ListTile(
title: Text(opt['texto']),
leading: getIcon(opt['icon']),
trailing: Icon(Icons.keyboard_arrow_right, color: Colors.blue,),
onTap: (){
print("Hello");
Navigator.pushNamed(context, opt['ruta']);
},
);
options..add(widgetTemp)
..add(Divider());
});
return options;
}
And this is my main.dart where I have the routes:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Components App',
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/' : (BuildContext context) => HomePage(),
'/alert' : (BuildContext context) => AlertPage(),
'/avatar' : (BuildContext context) => AvatarPage(),
},
onGenerateRoute: (settings){
print("Ruta llamada: ${settings.name }");
},
);
}
}

You forgot to add the leading /:
Navigator.pushNamed(context, '/${opt['ruta']}');
The route name needs to match the generators exactly.

To prevent this kind of errors always write your route names in a separated file like constants of something. That way you sure that every route name used in pushNamed is equal to what onGenerateRoute has.

The right answer right now is:
Navigator.of(context,
rootNavigator: true).pushNamed('/${element['ruta']}');

Related

Flutter navigate to main screen instead of previous

I just started working with Flutter and I'm not gonna lie its amazing. One "problem" I have encountered, though, is that when you hit the back key It will navigate to the previous screen. I know that thats what should happen, but how can I change that? Also, the appBar button does the same, so I'm looking for a "fix" for that one as well.
If you require any code, please, let me know! Have a great day, and sorry for any misspellings!
First, you need to set names for your routes in the MaterialApp:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => MyHomePage(),
'/second': (context) => Page2(),
'/third': (context) => Page3(),
},
);
}
}
Then you need to wrap the Scaffold of the third page (or any another page from which you want to navigate back to the HomePage) with a WillPopScope to change what happens when user presses the back button. Finally you need to use popUntil to navigate back to the HomePage.
This is the code for the build method of the third page:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
Navigator.of(context)
.popUntil(ModalRoute.withName("/"));
return Future.value(false);
},
child: Scaffold(
body: Center(
child: Container(
child: Text('third page'),
),
),
),
);
}
You can use
Navigator.of(context).pushNamedAndRemoveUntil(
'/', (Route<dynamic> route) => false);
Create a named route '/' that corresponds to the main screen and done.
Any more queries feel free to ask
If you specify "home" property in MaterialApp, there is no need to create any route. Navigation to homescreen can then be done as below:
Navigator.of(context).popUntil(ModalRoute.withName('/'));

How to stay on same state even after restarting app in flutter

I want to stay on the same page(such as otpPage)until and unless a condition is verified.
Condition networkutil.verify == "Y".
This is my main.dart
final routes = {
'/':(context)=> SignIn() ,
'/SignIn': (context)=> SignIn(),
'/Home': (context)=> Home(),
'/Register': (context) => Register(),
'/OtpPage': (context) => OtpPage()
};
class A extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: './',
routes: routes,
);
}
}
You can use BLoC Pattern, where you will put a condition :
if the user is registered, then Welcome page else, SignIn Page.
Hope you can get some idea from this repo:
https://github.com/samrat19/login_bloc

BlocProvider.of() called with a context that does not contain a Bloc - even that it does

First of, I do know how BLoC suppose to work, the idea behind it and I know the difference between BlocProvider() and BlocProvider.value() constructors.
For simplicity, my application has 3 pages with a widget tree like this:
App() => LoginPage() => HomePage() => UserTokensPage()
I want my LoginPage() to have access to UserBloc because i need to log in user etc. To do that, I wrap LoginPage() builder at App() widget like this:
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
),
);
}
}
That obviously works just fine. Then, if User logs in successfully, he is navigated to HomePage. Now, I need to have access to two different blocs at my HomePage so I use MultiBlocProvider to pass existing UserBloc further and create a brand new one named DataBloc. I do it like this:
#override
Widget build(BuildContext context) {
return BlocListener<UserBloc, UserState>(
listener: (context, state) {
if (state is UserAuthenticated) {
Navigator.of(context).push(
MaterialPageRoute<HomePage>(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
),
BlocProvider<DataBloc>(
create: (_) => DataBloc(DataRepository()),
),
],
child: HomePage(),
),
),
);
}
},
[...]
This also works. Problem happens when from HomePage user navigates to UserTokensPage. At UserTokensPage I need my already existing UserBloc that I want to pass with BlocProvider.value() constructor. I do it like this:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text('My App'),
actions: <Widget>[
CustomPopupButton(),
],
),
[...]
class CustomPopupButton extends StatelessWidget {
const CustomPopupButton({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
icon: Icon(Icons.more_horiz),
onSelected: (String choice) {
switch (choice) {
case PopupState.myTokens:
{
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
}
break;
case PopupState.signOut:
{
BlocProvider.of<UserBloc>(context).add(SignOut());
Navigator.of(context).pop();
}
}
},
[...]
When I press button to navigate to MyTokensPage i get error with message:
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building Builder(dirty):
BlocProvider.of() called with a context that does not contain a Bloc of type UserBloc.
No ancestor could be found starting from the context that was passed to BlocProvider.of<UserBloc>().
This can happen if:
1. The context you used comes from a widget above the BlocProvider.
2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.
Good: BlocProvider<UserBloc>(create: (context) => UserBloc())
Bad: BlocProvider(create: (context) => UserBloc()).
The context used was: CustomPopupButton
What am I doing wrong? Is it because i have extracted PopupMenuButton widget that somehow loses blocs? I don't understand what I can be doing wrong.
You can just wrap the Blocs you need to access through out the app by wrapping it at the entry point of the app like this
runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}
and you can access this bloc at anywhere of your app by
BlocProvider.of<UserBloc>(context).add(event of user bloc());
EDIT 10/03/2022
Since this thread became very popular I feel I need to add some comments.
This is valid solution if your goal is to use blocs that are not provided above your MaterialApp widget, but instead being declared somewhere down the widget tree by wrapping your widget (eg. some page) with BlocProvider making it possible for that widget to access the bloc.
It is easier to avoid problems by declaring all your blocs in MultiBlocProvider somewhere up the widget tree (like I said before), but this topic was not created with that in mind. Feel free to upvote and use this aproach described in Amesh Fernando response but do that knowing the difference.
I fixed it. Inside App widget i create LoginPage with
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
At LoginPage I simply wrap BlocBuilders one into another
Widget build(BuildContext context) {
return BlocListener<UserBloc, UserState>(
listener: (context, state) {
if (state is UserAuthenticated) {
Navigator.of(context).push(
MaterialPageRoute<HomePage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: BlocProvider<NewRelicBloc>(
create: (_) => NewRelicBloc(NewRelicRepository()),
child: HomePage(),
),
),
),
);
}
},
[...]
PopupMenuButton navigates User to TokenPage with
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
And that solved all my problems.
Solution
Method A: Access UserBloc provider instance directly without passing it
I prefer this solution since it requires less code.
A.1 Wrap CustomPopupButton instance with provider Consumer so it rebuilds itself whenever UserBloc notifies listeners of value changes.
Change this:
actions: <Widget>[
CustomPopupButton(),
],
To:
actions: <Widget>[
Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
return CustomPopupButton(),
});
],
A.2 Change Provider instance invocation inside the stateless widget to disable listening to value changes -- "listening" and resulting "rebuilds" are already done by Consumer.
A.2.1 Change this:
value: BlocProvider.of<UserBloc>(context),
To:
value: BlocProvider.of<UserBloc>(context, listen: false),
A.2.2 And change this:
BlocProvider.of<UserBloc>(context).add(SignOut());
To:
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
Method B: pass UserBloc provider instance
Same thing as Method A, but:
In A.1 you'd pass userBloc like this: return CustomPopupButton(userBloc: userBloc),.
You'd declare final UserBloc userBloc; member property inside CustomPopupButton.
In A.2 you'd do this: userBloc.add(SignOut()); instead of BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
Explanation
flutter_bloc is using Provider, to be aware what's going on it's better understand Provider. Please refer to my answer here to understand my answer to your question, and to understand Provider and listen flag better.
Change name of context in builder whether in bottomSheet or materialPageRoute.
So that bloc can access parent context through context
unless it's going to take context from builder (bottom sheet). This can lead
to an error which you can't reach the instance of bloc .
showModalBottomSheet(
context: context,
builder: (context2) { ===> change here to context2
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: widgetA(),
),
}
You need to either decompose your widget into two widgets (which I recommend for testability reasons) or use a Builder widget to get a child context.
class MyHomePage extends StatelessWidget { #override Widget build(BuildContext context) { return BlocProvider( create: (_) => TestCubit(), child: MyHomeView(), ); } } class MyHomeView extends StatelessWidget { #override Widget build(BuildContext context) { return Scaffold( body: Center( child: RaisedButton(onPressed: () => BlocProvider.of<TestCubit>(context)...) ), ); } }
source: solved by Felix Angelov, https://github.com/felangel/bloc/issues/2064
you don't have to use BlocProvider.value() to navigate to another screen, you can just wrap MaterialApp into BlocProvider as a child of it

Flutter provider loosing value when navigating to another screen

I'm new to Flutter and provider package so any assistance would be great, so the issue I have my main.dart file which is as follows:
void main() => runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
home: ChangeNotifierProvider(
create: (_) => InterestingMomentProvider(),
child: Home(),
),
),
);
This builds my Home widget, I won't post it all as It's extremely large, however, what happens is I click a button and it passes in a string to the provider class an adds it to the list which is outlined as follows:
import 'package:flutter/widgets.dart';
class InterestingMomentProvider extends ChangeNotifier {
List<String> _moments = [];
List<String> get moments => _moments;
void addMoment(String time){
_moments.add(time);
}
int momentsTotal(){
return _moments.length;
}
}
Adding a breakpoint on the addMoment method I can confirm _moments has all the strings.
I then press a button which navigates to another screen, the navigation code is as follows:
Navigator.push(context, MaterialPageRoute(builder: (context) => MomentsRecorded()),);
MomentsRecorded widget is as follows:
class MomentsRecorded extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Moments'),
centerTitle: true,
),
body: Center(
child: MomentsList()
),
);
}
}
the first error was:
Could not find the correct Provider<InterestingMomentProvider> above this Consumer<InterestingMomentProvider> Widget
To fix, please:
* Ensure the Provider<InterestingMomentProvider> is an ancestor to this Consumer<InterestingMomentProvider> Widget
* Provide types to Provider<InterestingMomentProvider>
* Provide types to Consumer<InterestingMomentProvider>
* Provide types to Provider.of<InterestingMomentProvider>()
* Always use package imports. Ex: `import 'package:my_app/my_code.dart';
* Ensure the correct `context` is being used.
I then tweaked the body to look like the following and the error dissappeared:
body: Center(
child: ChangeNotifierProvider(
create: (_) => InterestingMomentProvider(),
child: MomentsList())
),
However inside MomentLists widget, I try to loop through the list of moments from the provider class, however when debugging _moments is 0 ?
MomentsList widget:
class MomentsList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<InterestingMomentProvider>(
builder: (context, momentData, child){
return momentData.momentsTotal() > 0 ? ListView.builder(
itemCount: momentData.momentsTotal(),
itemBuilder: (BuildContext context, int index) {
final moment = momentData.moments[index];
return ListTile(
title: Text(moment),
);
}
) : Center(child: Text('no moments recorded'),);
}
);
}
}
Can someone please explain why this maybe?
This is happening, because your provider is defined in home property of MaterialApp, so when you change the route the provider will be removed too.
Solution: move the provider above the MaterialApp like this:
void main() => runApp(
ChangeNotifierProvider(
create: (_) => InterestingMomentProvider(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
home: Home()
),
),
);
If you are afraid that this isn't right - checkout the docs, they are doing the same

Navigator from MaterialApp builder

I'm trying to embed my whole App into an AppBar and a Footer.
So I tried giving a custom Builder to my MaterialApp which look like this (I replaced the footer and the app bar by a button for clarity)
import 'package:epicture/scenes/Landing.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
initialRoute: '/',
builder: (context, child) => Container(
child: FlatButton(
child: Text('Click me'),
onPressed: () => Navigator.of(context).pushNamed('/app'),
)),
// In my current code
builder: (context, child) => Embedder(child),
routes: <String, WidgetBuilder>{
'/': (context) => Landing(),
'/app': (context) => Text('My App !'),
},
);
}
}
But on press of the 'Click me' button, an error is raised saying that the context doesn't have a Navigator
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
But actually, the button is a footer that can be clicked to change page from every page.
I would like to know how to have access to the navigator from the custom builder and if I simply head toward the wrong way for having the same pattern in every page of my application (Footer + header)
You cannot access Navigator with context from inside the builder as any widget returned by this builder will be parent for the navigator.
So, what do I do? Can I access navigator here, how?
Yeah! You can, create a GlobalKey and pass it to your MaterialApp. Then use that key to access Navigator inside your builder.
Example:
import 'package:epicture/scenes/Landing.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final GlobalKey<NavigatorState> _navigator = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
initialRoute: '/',
navigatorKey: _navigator,
builder: (context, child) {
return Container(
child: FlatButton(
child: Text('Click me'),
onPressed: () => _navigator.currentState.pushNamed('/app'),
),
);
},
routes: <String, WidgetBuilder>{
'/': (context) => Landing(),
'/app': (context) => Text('My App !'),
},
);
}
}
Hope that helps!
Rename this context variable to something else
builder: (context, child)
You are getting name clashs for the context you are using here:
onPressed: () => Navigator.of(context).pushNamed('/app'),