Flutter GoRouter won't let me pass any value which is not String as parameter.
Error Received
The following _TypeError was thrown while handling a gesture:
type 'int' is not a subtype of type 'Iterable<dynamic>'
Page which receives the int parameter:
class ItemOne extends StatelessWidget {
final int id;
const ItemOne({super.key, required this.id});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Item 1'),
),
body: Text('This is page for with id: $id'),
);
}
}
GoRouter defination
GoRoute(
path: '/one',
name: 'one',
builder: (context, state) {
return ItemOne(
id: state.queryParams['idGiven'] as int,
);
},
),
Button which passes the int value
IconButton(
onPressed: () => context.pushNamed(
'one',
queryParams: <String, dynamic>{
'idGiven': 111,
},
),
icon: const Text('Push One'),
),
Yes, indeed.
queryParams is a map from String to String.
If you want to pass something else, you need to convert it to a string and back.
Or use the extra field if you are passing whole objects.
Related
itemBuilder: (context, index) => MyImage( image: API.image +'/' `your text`+snapshot.data[index['MainPicture'].toString(), title: snapshot.data[index]['productName'],`your text`
subname: snapshot.data[index]['productSubname'],`your text`
price: snapshot.data[index][price].toString(),`your text`
discount: '% ' +
snapshot.data[index]['productDiscount'].toString(),`your text`
),
I want these parametres to make them to another Screen your text
Use a navigator like go_router
Later follow the steps accordingly to pass your parameters.
Definition
GoRoute(
path: '/sample/:id1/:id2', š Defination of params in the path is important
name: 'sample',
builder: (context, state) => SampleWidget(
id1: state.params['id1'],
id2: state.params['id2'],
),
),
Passing the params
ElevatedButton(
onPressed: () {
var param1 = "param1";
var param2 = "param2";
context.goNamed("sample", params: {'id1': param1, 'id2': param2});
},
child: const Text("Hello"),
),
Receiver widget:
class SampleWidget extends StatelessWidget {
String? id1;
String? id2;
SampleWidget({super.key, this.id1, this.id2});
#override
Widget build(BuildContext context) {
...
}
}
And refer this answer: Flutter: go_router how to pass multiple parameters to other screen?
I already have data stored on firestore , but now i'd like to add this data into an empty list using ChangeNotifier , similar to this example -https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple .
so below i'd like to add data stored on firebase into the _cart list , with the method I tried below I got the error The argument type 'List<Menu>' can't be assigned to the parameter type 'Menu'(would like to know why?) , it also contains the ui for where i'd like to map the data from cart list into the dialog sheet:
class AddList extends ConsumerWidget {
const AddList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final menuAsync = ref.watch(menuProvider);
final model = ref.read(cartProvider);
return Scaffold(
appBar: AppBar(
title: const Text("menu"),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
onPressed: () async {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("cart"),
content: Column(
children: const [
...model.cart.map((item) => Text(item.mealName)),
],
),
);
});
},
),
body: menuAsync.when(
data: (menu) => Column(
children: menu
.map(
(e) => Card(
child: ListTile(
title: Text(e.mealName),
subtitle: Text(e.price),
trailing: IconButton(
onPressed: () {
model.addProduct(menu);//where im getting error
},
icon: const Icon(Icons.add)))),
)
.toList(),
),
error: (e, s) => Center(child: Text("$e")),
loading: () => const Center(child: CircularProgressIndicator())),
);
}
}
below im using changenotifier to modify the cart list:
final cartProvider =
ChangeNotifierProvider<CartNotifier>((ref) => CartNotifier());
class CartNotifier extends ChangeNotifier {
final List<Menu> _cart = [];
List<Menu> get cart => _cart;
void addProduct(Menu menu) {
_cart.add(menu);
notifyListeners();
}
void removeProduct(Menu menu) {
_cart.remove(menu);
notifyListeners();
}
}
how im reading data from firestore :
final menuProvider = StreamProvider<List<Menu>>(
(ref) => ref.read(addMealRespositoryProvider).menuSearchStream);
Stream<List<Menu>> get menuSearchStream =>
_firestore.collection("menu").snapshots().map(
(event) => event.docs.map((e) => Menu.fromFirestore(e)).toList(),
);
snippet of my data model:
class Menu {
String mealName;
String price;
Menu({
required this.mealName,
required this.price,
});
Map<String, dynamic> toMap() {
return {
"mealName": mealName,
"price": price, },
factory Menu.fromFirestore(DocumentSnapshot doc) {
final map = doc.data() as Map<String, dynamic>;
return Menu(
mealName: map["mealName"] ?? '',
price: map["price"] ?? '',
);
}
}
Iām having this error above when I try to compare the parameter I get from the route with the value of a list.
MealDetailScreen widget
import 'package:flutter/material.dart';
import '../dummy_data.dart';
class MealDetailScreen extends StatelessWidget {
static const detailScreenRouteName = '/meal-detail';
#override
Widget build(BuildContext context) {
final mealId = ModalRoute.of(context)!.settings.arguments;
final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId);
return Scaffold(
appBar: AppBar(title: Text('$mealId')),
body: Container(
child: Column(
children: [
Container(
height: 300,
width: double.infinity,
child: Image.network(
selectedMeal.imageUrl,
fit: BoxFit.cover,
),
)
],
),
),
);
}
}
If i try to add the optional argument 'orElse' to the firstWhere function i still get an error: this time it is The return type 'Null' isn't a 'Meal', as required by the closure's context.
This is the list im using to compare the id.
const DUMMY_MEALS = [
Meal(
isVegetarian: false,
isLactoseFree: false,
isVegan: false,
id: 'm1',
categories: [
'c1',
'c2',
],
title: 'Spaghetti with Tomato Sauce',
affordability: Affordability.Affordable,
complexity: Complexity.Simple,
imageUrl: '...',
duration: 20,
ingredients: ['4 Tomatoes', '...'],
steps: ['Cut the tomatoes and the onion into small pieces.', '...'],
isGlutenFree: false,
),
];
And this is how i pass the id as parameter
void selectMeal(BuildContext context) {
Navigator.of(context)
.pushNamed(MealDetailScreen.detailScreenRouteName, arguments: {id});
}
Meal model
import 'package:flutter/foundation.dart';
enum Complexity { Simple, Challenging, Hard }
enum Affordability { Affordable, Pricey, Luxurious }
class Meal {
final String id;
final List<String> categories;
final String title;
final String imageUrl;
final List<String> ingredients;
final List<String> steps;
final int duration;
final Complexity complexity;
final Affordability affordability;
final bool isGlutenFree;
final bool isLactoseFree;
final bool isVegan;
final bool isVegetarian;
const Meal(
{required this.id,
required this.categories,
required this.title,
required this.imageUrl,
required this.ingredients,
required this.steps,
required this.duration,
required this.complexity,
required this.affordability,
required this.isGlutenFree,
required this.isLactoseFree,
required this.isVegan,
required this.isVegetarian});
}
While passing arguments: {id} you are passing _HashSet<String>. But for single value id string will be enough, else use map.
In this case passing argument will be
Navigator.of(context)
.pushNamed(MealDetailScreen.detailScreenRouteName, arguments: id);
While iterating the DUMMY_MEALS list it is possible that we will get an id that is not included on DUMMY_MEALS list. In this case you can create and pass emptyMeal on orElse state or just use try catch to handle exception.
Meal? selectedMeal;
try {
final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId);
} catch (e) {
print(e.toString());
}
While the selectedMeal is nullable, we can check if it contains meal or not.
return Scaffold(
appBar: AppBar(title: Text('$mealId')),
body: selectedMeal == null
? Text("Cound not find data")
: Container(
child: Column(
children: [
Text(selectedMeal.title),
],
),
),
);
The answer has been given. But I want mention that if you're using pushNamed method, it recommended that you manage passing parameters with onGenerateRoute . So you don't have nullcheck arguments or context needed.
ModalRoute.of(context)?.settings?.arguments?
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == PassArgumentsScreen.routeName) {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
assert(false, 'Need to implement ${settings.name}');
return null;
},
)
References: Pass arguments to a named route
I am new to Flutter and attempting sample mutual fund app to cover all basic widgets.
Requirement -> After selecting MF scheme, when user confirms on "buyNow" screen, corresponding scheme should get added to global dynamic list in "Cart" screen. This is basically a cart which is accessible to user on any screen, similar to shopping cart. I want to update cart list on "buyNow" screen and display same on "Cart" screen.
I have followed link to learn about 'provider' method of flutter to solve this, but not able to do.
PFB code
Main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MaterialApp(
home: Schemelist(),
routes: {
'/landing': (context) => Landing(),
'/schemelist': (context) => Schemelist(),
'/schemeBuy': (context) => SchemeBuy(),
'/buyNow': (context) => BuyNow(),
'/cart': (context) => Cart(),
},
),
),
);
}
Cartmodel.dart
import 'package:flutter/foundation.dart';
class CartModel with ChangeNotifier{
String schemeName;
String type;
String fromDate;
String toDate;
double amount;
List<CartModel> _cartList=[];
CartModel({this.amount,this.fromDate,this.schemeName,this.toDate,this.type});
void addToCart(CartModel cartObj){
_cartList.add(cartObj);
notifyListeners();
}
double get totalAmount =>
_cartList.fold(0, (total, current) => total + current.amount);
}
BuyNow.dart
RaisedButton(
onPressed: () {
_cart=new CartModel(amount:1000,fromDate:_dateTime.toString(),schemeName:widget.investmentObj.schemeName,toDate:_dateTime1.toString(),type:'SIP');
var cart = Provider.of<CartModel>(context);
cart.addToCart(_cart);
Navigator.pushNamed(context, '/cart');
},
child: Text('Yes'),
),
Cart.dart //where I will display dynamic list
Widget build(BuildContext context) {
var cart = Provider.of<CartModel>(context);
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: ListView.builder(
itemCount: --not able to access list--
itemBuilder: (context, index) => ListTile(
title: Text(
-------
),
),
),
);
}
First we should modify CartModel class. The fields (such as schemeName) should belong to the CartItem class, and the CartModel should only do its own thing (addToCart and others).
class CartModel with ChangeNotifier {
List<CartItem> _itemList = [];
// An unmodifiable view of the items in the cart.
UnmodifiableListView<CartItem> get itemList => UnmodifiableListView(_itemList);
void addToCart(CartItem item) {
_itemList.add(item);
notifyListeners();
}
double get totalAmount => _itemList.fold(0, (total, current) => total + current.amount);
}
class CartItem{
String schemeName;
String type;
String fromDate;
String toDate;
double amount;
CartItem({this.amount, this.fromDate, this.schemeName, this.toDate, this.type});
}
Then, in Cart.dart
Widget build(BuildContext context) {
var itemList = Provider.of<CartModel>(context).itemList;
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: ListView.builder(
itemCount: itemList.length,
itemBuilder: (_, index) {
var item = itemList[index];
return Text(item.schemeName);
},
),
);
}
You will get a error while click RaisedButton:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix it, edit BuyNow.dart:
RaisedButton(
onPressed: () {
var _item = CartItem(amount: 1000, fromDate: _dateTime.toString(), schemeName: widget.investmentObj.schemeName, toDate: _dateTime1.toString(), type: 'SIP');
//just set listen to false
var cart = Provider.of<CartModel>(context, listen: false);
cart.addToCart(_item);
Navigator.pushNamed(context, '/cart');
},
child: Text('Yes'),
),
I am trying to build multi level list view, when we tap child items, it should pass parameter to other page named QuizOptionsDialog.
1) I am able to print the root.name using print(root.name); using onTap
2) but when we try to navigate using following code it is showing undefined name 'context'
Full Code and error
enter code hereCode page 1/2
enter code hereCode page 2/2
enter code hereError
import 'package:flutter/material.dart';
import 'package:iti/quiz/ui/widgets/quiz_options.dart';
class ExpansionTileDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('List of Question Papers'),
),
body: ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) => CategoryItem(
data[index],
),
),
),
);
}
}
// Welcome to another flutter tutorial
// In this video we will see how to create a multi-level Expansion List
// First Let's create a class for each row in the Expansion List
class Category {
final String id;
final String name;
final List<Category>
children; // Since this is an expansion list ...children can be another list of entries
Category(this.id, this.name, [this.children = const <Category>[]]);
}
// This is the entire multi-level list displayed by this app
final List<Category> data = <Category>[
Category(
'1',
'Main Category',
<Category>[
Category(
'1.1',
'Sub Category',
<Category>[
Category('1.1.1', 'Sub-Sub Category', <Category>[
Category('1.1.1.1', 'Sub-Sub-Sub Category',),
Category('1.1.1.2', 'Sub-Sub-Sub Category',),
]),
Category('1.1.2','Sub-Sub Category',
<Category>[
Category('1.1.2.1', 'Sub-Sub-Sub Category',),
Category('1.1.2.2', 'Sub-Sub-Sub Category',),
]
),
Category('1.1.3', 'Sub-Sub Category',
<Category>[
Category('1.1.3.1', 'Sub-Sub-Sub Category',),
Category('1.1.3.2', 'Sub-Sub-Sub Category',),
]
),
],
),
],
),
];
// Create the Widget for the row
class CategoryItem extends StatelessWidget {
const CategoryItem(this.category);
final Category category;
// This function recursively creates the multi-level list rows.
Widget _buildTiles(Category root) {
if (root.children.isEmpty) {
return ListTile(
title: Text(root.name),
onTap: () {
print(root.name);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuizOptionsDialog(category: category,),
),
);
},
);
}
return ExpansionTile(
key: PageStorageKey<Category>(root),
title: Text(root.name),
children: root.children.map<Widget>(_buildTiles).toList(),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(category);
}
_categoryPressed(BuildContext context,Category category) {
showModalBottomSheet(
context: context,
builder: (sheetContext) => BottomSheet(
builder: (_) => QuizOptionsDialog(category: category,),
onClosing: (){},
),
);
}
}
Change your CategoryItem class to also accept a BuildContext context variable in the constructor and assign it to a BuildContext variable like you did with category, and then pass that context in from the main widget when creating new CategoryItem. This will give you access to a context and should allow you to do your navigation.
To do so:
Storing the context in your CategoryItem class so each instance has access to it.
class CategoryItem extends StatelessWidget {
const CategoryItem(this.category, this.context);
final Category category;
final BuildContext context;
Updating the ListView.builder() to instantiate the updated CategoryItem class appropriately and pass the context.
body: ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) => CategoryItem(
data[index], context
),
),
And now you should have access to the BuildContext for your page where you needed it.