Passing Arguments between pages but show this error - flutter

I m trying to pass arguments between pages but I m getting this error:
FlutterError (Could not find a generator for route RouteSettings("detail", Instance of 'Commodity') in the _WidgetsAppState.
Make sure your root app widget has provided a way to generate
this route.
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.)
I don't know why because 'detail' page already setted
This is my Main App
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: WelcomePage.routeName,
routes: {
'/welcome': (_) => const WelcomePage(),
'detail': (_) => SubCommoditiePage(),
},
);
}
}
This is my From Page
class DisplayOptions extends StatelessWidget {
final List<Commodity> _optionsToDisplay;
const DisplayOptions(this._optionsToDisplay);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _optionsToDisplay.length,
itemBuilder: (context, index) {
final opt = _optionsToDisplay[index];
return ListTile(
title: Text(opt.label),
subtitle: Text(opt.shortLabel),
onTap: () {
Navigator.pushNamed(context, 'detail', arguments: _optionsToDisplay[index]);
});
});
}
}
This is my Destination Page
class SubCommoditiePage extends StatefulWidget {
const SubCommoditiePage({Key? key}) : super(key: key);
static const String routenName = 'detail';
static Route route() {
return MaterialPageRoute(
builder: (_) => const SubCommoditiePage(),
settings: const RouteSettings(name: routenName));
}
#override
_SubCommoditiePageState createState() => _SubCommoditiePageState();
}
class _SubCommoditiePageState extends State<SubCommoditiePage> {
#override
Widget build(BuildContext context) {
final Commodity commoditySeleted =
ModalRoute.of(context)?.settings.arguments as Commodity;
return Scaffold(
appBar: AppBar(
title: Text(commoditySeleted.label),
),
);
}
}

For initial/homepage, use /. In this case your WelcomePage.routeName will be /;
and route
routes: {
WelcomePage.routeName: (_) => WelcomePage(),
..........
},
You don't need to pass initialRoute while the root '/' will be selected.
For more about named-routes

The way I was able to solve it was:
I evidently didn't know why flutter didn't recognize my 'detail' page.
And I had to do it this way:
This is my From Page
in my ontap, I used
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SubCommoditiePage(
seleted: _optionsToDisplay[index],
)));
like that:
class DisplayOptions extends StatelessWidget {
final List<Commodity> _optionsToDisplay;
const DisplayOptions(this._optionsToDisplay);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _optionsToDisplay.length,
itemBuilder: (context, index) {
final opt = _optionsToDisplay[index];
return ListTile(
title: Text(opt.label),
subtitle: Text(opt.shortLabel),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SubCommoditiePage(
seleted: _optionsToDisplay[index],
)));
});
});
}
}
adding to my page: final Commodity seleted; and with widget.selected... I was able to use it perfectly!
class SubCommoditiePage extends StatefulWidget {
final Commodity seleted;
const SubCommoditiePage({required this.seleted, Key? key}) : super(key: key);
static const String routenName = 'detail';
#override
_SubCommoditiePageState createState() => _SubCommoditiePageState();
}
class _SubCommoditiePageState extends State<SubCommoditiePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.seleted.label),
backgroundColor: const Color.fromRGBO(37, 59, 128, 5),
),
body: ListView.builder(
itemCount: widget.seleted.subCommodities.length,
itemBuilder: (context, index) {
final opt = widget.seleted.subCommodities[index];
return ListTile(
title: Text(opt.label),
onTap: () {},
);
}));
}
}

Related

Could not find the correct Provider above this Test widget

======== Exception caught by gesture ===============================================================
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider above this Test Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
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 Test is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
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
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
I am building an Widget "Test" to search users by their username. This is the widget Test with Bloc.
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: BlocListener<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
child: Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocBuilder<DonorsCubit, DonorsState>(
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState) return const Icon(Icons.circle);
return const SizedBox();
},
),
),
),
),
);
}
}
I used this cubit to manage states.
class DonorsCubit extends Cubit<DonorsState> {
List<MyUser> users = <MyUser>[];
final FirebaseDBRepo _firebaseDBRepo = FirebaseDBRepo();
late StreamSubscription _streamSubscription;
DonorsCubit() : super(DonorsInitialState()) {
_streamSubscription =
_firebaseDBRepo.usersStream().listen((List<MyUser> users) {
this.users = users;
});
}
void searchDonors({required String? searchKey}) {
emit(DonorsLoadingState());
List<MyUser> searchedUser = <MyUser>[];
searchedUser.clear();
if (searchKey == null) {
emit(DonorsLoadedState(users: users));
} else {
for (MyUser user in users) {
if (user.username!.toLowerCase().contains(searchKey.toLowerCase())) {
searchedUser.add(user);
}
}
emit(DonorsLoadedState(users: searchedUser));
}
}
#override
Future<void> close() {
_streamSubscription.cancel();
return super.close();
}
}
abstract class DonorsState extends Equatable {
const DonorsState();
}
class DonorsLoadingState extends DonorsState {
#override
List<Object> get props => [];
}
class DonorsInitialState extends DonorsState {
#override
List<Object> get props => [];
}
class DonorsLoadedState extends DonorsState {
final List<MyUser> users;
const DonorsLoadedState({required this.users});
#override
List<Object?> get props => [users];
}
The problem you get is related to how the provider package works. In order to access the cubit, you should provide it above in the widget tree. Now, you provide and listen to the cubit in the same context. There are several ways how you could handle it.
Use the Builder widget.
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: Builder(
builder: (context) => BlocListener<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
child: Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocBuilder<DonorsCubit, DonorsState>(
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState)
return const Icon(Icons.circle);
return const SizedBox();
},
),
),
),
),
),
);
}
}
Split your widget into two and provide your cubit in the parent widget:
class TestWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: const Test(),
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
child: Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocBuilder<DonorsCubit, DonorsState>(
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState) return const Icon(Icons.circle);
return const SizedBox();
},
),
),
),
);
}
}
I am a fan of option 2 since it is more clear that you are splitting your code and working in separate contexts.
BONUS
Instead of using BlocListener and BlocBuilder separately, you could use the BlocConsumer widget:
class TestWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: const Test(),
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocConsumer<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState) return const Icon(Icons.circle);
return const SizedBox();
},
),
),
);
}
}
I have the same problem, I use the MultiProvider to list my providers like this:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Example()),
],
child: MaterialApp(
title: 'Example',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme)
),
// here I set my first screen...
home: HomePage(),
),
);
}

Flutter: Custom Navigator ModalRoute.of does not work properly

I want to change my selected tabIndex when user uses back button.
So, I edited my willPopScope to get current Route name. I am using
nested navigation. So my application has two navigators.
When I want to get current route name after pop action, It returning to me mainNavigatorKey's routeName.But I give it the coachNavigatorKey context..
Also I can not get the arguments from previous page data is always null.
ModalRoute.of(NavigatorKeys.coachNavigator.currentState!.context)!.settings.arguments;
WillPopScope(
onWillPop: () async {
await NavigatorKeys.coachNavigator.currentState!.maybePop();
var s = ModalRoute.of(NavigatorKeys.coachNavigator.currentState!.context)!.settings.name;
context.read<CoachMainCubit>().setTabIndexByName(s!);
return false;
},
Here is my Custom Navigator..
class CustomNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final List<RouteObserver>? routeObserverList;
final Map<String, WidgetBuilder> routeList;
const CustomNavigator({Key? key, required this.navigatorKey, required this.routeList, this.routeObserverList}) : super(key: key);
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: "/",
observers: routeObserverList ?? [],
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (BuildContext context) => routeList[settings.name]!(context),
settings: RouteSettings(name: settings.name),
);
},
);
}
}
Here is how I call my nested navigator:
class CoachMainView extends StatelessWidget {
const CoachMainView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (CoolAlertHelper().isLoadingDialogExists()) {
return false;
}
await NavigatorKeys.coachNavigator.currentState!.maybePop();
return false;
},
child: BlocProvider(
create: (context) => CoachMainCubit(context),
child: const CoachMainScreen(),
),
);
}
}
class CoachMainScreen extends StatelessWidget {
const CoachMainScreen({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: buildCustomNavigator(context),
bottomNavigationBar: buildBottomNavigationBar(context),
);
}
Widget buildBottomNavigationBar(BuildContext context) {
return SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: context.dynamicWidth(0.002), vertical: context.dynamicHeight(0.000)),
child: BlocSelector<CoachMainCubit, CoachMainState, int>(
selector: (state) {
return context.read<CoachMainCubit>().currentTabIndex;
},
builder: (context, state) {
return buildBottomNavBar(context);
},
),
),
);
}
CustomNavigator buildCustomNavigator(BuildContext context) {
return CustomNavigator(
navigatorKey: NavigatorKeys.coachNavigator,
routeObserverList: [CoachRouteObserver(context)],
routeList: CoachScreenConstants().getCoachRoutes(context),
);
}

Flutter - Parse data to statefulWidget [duplicate]

This question already has answers here:
Passing data between screens in Flutter
(13 answers)
Closed 2 years ago.
I am fairly new in Flutter. I have an issue where I am stock. I am trying to parse data from one widget to another StatefulWidget.
I have this widget where I try to parse data from
class MaltInput extends StatefulWidget {
#override
_MaltInputState createState() => _MaltInputState();
}
class _MaltInputState extends State<MaltInput> {
List<String> malt = ['Malt 1', 'Malt 2', 'Malt 3'];
String maltpick = "";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Malt input'),
),
body: ListView.builder(
itemCount: malt.length,
itemBuilder: (context, index){
return Card(
child: ListTile(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Test(malt[index]),
));
},
title: Text(malt[index]),
),
);
},
),
);
}
}
Parse to this widget
class Test extends StatefulWidget {
String malt;
Test({this.malt});
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
String malt;
_TestState({this.malt});
List<String> items = [];
final TextEditingController ectrl = TextEditingController();
#override
Widget build(BuildContext context) {
String maltpick;
maltpick = (widget.malt);
//widget.malt = "";
return Scaffold(
appBar: AppBar(
title: Text('Dynamic content'),
),
body: Column(
children: <Widget>[
//
RaisedButton(
child: Text('Add malt'),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => MaltInput()));
}
),
Text('Header.....'),
Text(maltpick),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext ctxt, int Index){
return Text(items[Index]);
}
),),
],
),
);
}
}
The error is in this line : builder: (context) => Test(malt[index]),
Error code: Error: Too many positional arguments: 0 allowed, but 1 found.
Try removing the extra positional arguments.
builder: (context) => Test(malt[index]),
If you use named parameters - the ones in {} - in your constructor
Test({this.malt});
you need to invoke it like this
MaterialPageRoute(builder: (context) => Test(malt: malt[index]))
You can check the documentation on the different kinds of parameters here.
Replace the below code.
class Test extends StatefulWidget {
String malt;
Test({this.malt});// here I Changed
#override
_TestState createState() => _TestState();
}
To
class Test extends StatefulWidget {
String malt;
Test(this.malt);// here it will be
#override
_TestState createState() => _TestState();
}
And remove String malt; and _TestState({this.malt}); code from test class.
class _TestState extends State<Test> {
String malt;// remove this line
_TestState({this.malt});// remove this line too

Why is the BlocBuilder never called when bloc is not set explicitly

This is an example of a Flutter counter app. I instantiate the Counter with a Bloc like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Counter(CounterBloc()),
);
}
}
This code below works fine. Event is dispatched and the "builder" method is called.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment)),
);
}
}
The code below does not work. The event is dispatched but the builder is never called.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment)),
),
);
}
}
I found out that I can set property "bloc" on a "BlocBuilder" but I'd expect it's not necessary.
Why the difference in behavior?
I believe the CounterEvent.increment from not working snippet won't get dispatched and instead will throw an error BlocProvider.of() called with a context ... because you use the same context where you provided the bloc.
This code works because it's a new context after BlocProvider
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Builder(
builder: (context) => Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment),
),
),
),
);
}
}
This code also works because we explicitly use the bloc instance from the constructor instead of calling BlocProvider.of() and using the bloc instance provided via BlocProvider.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
bloc: bloc,
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => bloc.add(CounterEvent.increment),
),
),
);
}
}
Both snippets above will work but it's not exactly the "correct" way.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Counter(
CounterBloc(), // <=() You need a work around to dispose this instance
),
);
}
}

Proper page navigation

I am trying to navigate to a page called contactView. I have made a list of contacts and I wait to navogate to a contact when I click on there name. This is what I have so far. I am stuck trying to get the navigation to work. Any help would be great.
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return new ListView.builder(
padding: new EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return new _ContactListItem(_contacts[index]);
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact())
);
},
itemCount: _contacts.length,
);
}
}
Here are few things that I can immediately point out (Problems):
onPressed is not available on ListView.builder() , you may check
here:
https://docs.flutter.io/flutter/widgets/ListView/ListView.builder.html
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact()) this won't execute because it is after return
Suggestions:
You might need to wrap your _ContactListItem() inside a
GestureDetector and implement an onTap callback
Sample Code:
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
//TODO: Insert your navigation logic here
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
ContactView(_contacts[index])));
},
child: _ContactListItem(_contacts[index]),
);
},
itemCount: _contacts.length,
);
}
}
Another option could be to change the implementation of
_ContactListItem() and may be use a ListTile and implement an onTap in ListTile, you can find it here: https://docs.flutter.io/flutter/material/ListTile-class.html
You may also try to implement named routes, here is a tutorial for
that https://flutter.io/cookbook/networking/named-routes/
I hope this was helpful in someway, let me know if I misinterpreted the question.
See if the below is what you're looking for.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Contact Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Contact Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _contacts = [
Contact(name: 'John'),
Contact(name: 'Mary'),
Contact(name: 'Suzy')
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
title: const Text(
'Contact Demo',
style: const TextStyle(color: Colors.white),
),
),
body: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Contact #$index'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) =>
ContactView(contact: _contacts[index]),
));
},
);
},
),
);
}
}
class Contact {
Contact({this.name});
final String name;
}
class ContactView extends StatelessWidget {
ContactView({this.contact});
final Contact contact;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(contact.name),
),
body: Center(
child: Text(contact.name),
),
);
}
}