I've only been coding in Flutter for a few weeks now and I would like to know if it is possible just to navigate to a page using named routes that has received arguments from another page? The main objective is to navigate to the Cart Screen from two different pages where one passes an argument while the other doesn't. Here is my code below to explain my question:
This is the first part of the code which navigates to the cart screen after passing arguments id and quantity
class ItemDetailsState extends State<ItemDetails> {
int quantity = 1; //quantity
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = routes["id"]; //id
return Scaffold(
......
InkWell(
onTap: () {
Navigator.of(context).pushNamed('/cart-screen', arguments: { //This navigates to the cart screen passing arguments id and quantity
'id': routes["id"],
'quantity': quantity,
});
Provider.of<CartItemProvider>(context, listen: false)
.addItems(id, name, restaurantName, price, quantity);
},
);
}
}
This is the Cart Screen that receives the arguments and filters data from a Provider Class:
class CartScreen extends State<CartScreenState> {
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = routes['id']; //Received Arguments
final quantity = routes['quantity']; //Received Arguments
final provider =
Provider.of<PopularDishesProvider>(context).getProductById(id); //Provider that filters the data as per ID
My idea is to navigate to the Cart Screen page from another page like this but it throws the below error:
class HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return Scaffold(
..............
body: Row(
children: [
InkWell(
onTap: () => Navigator.of(context)
.pushReplacementNamed('/cart-screen'), //Navigate to the Cart Screen
child: const Icon(
Icons.shopping_cart_outlined,
color: Colors.grey,
size: 30,
),
),
InkWell(
onTap: () {},
child: const Icon(
Icons.notifications_none_outlined,
color: Colors.grey,
size: 30,
),
)
],
)
The method '[]' was called on null.
Receiver: null
Tried calling: []("id")
The above error I believe is owing to the fact that I'm trying to just navigate to '/cart-screen' without passing any argument in the HomeScreenState widget. I need suggestions to know if there's any way to get around this?
The route is declared in the main.dart file as it should like
routes : {
'/cart-screen': (context) => CartScreen(),
}
You can check null value using
#override
Widget build(BuildContext context) {
var arguments3 = ModalRoute.of(context)!.settings.arguments;
var routes=
arguments3!=null? arguments3 as Map<String, dynamic>:{};
final id = routes['id']??0; //Received Arguments
final quantity = routes['quantity']??0; //Received Arguments
final provider =
Provider.of<PopularDishesProvider>(context).getProductById(id);
We can pass argument with the help of argument property in pushnamed method
Navigator.pushNamed(context, AppRoutes.Page1,
arguments: {"name": "lava", "body": "chi"});
Receive value
var arguments3 = ModalRoute.of(context)!.settings.arguments;
var arguments2 =
arguments3!=null? arguments3 as Map<String, dynamic>:{};
May like this
SAmple Code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
AppRoutes.home: (context) => Home(),
AppRoutes.Page1: (context) => Page1(),
},
title: _title,
// home: ,
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("title")),
body: const Center(
child: MyStatelessWidget(),
),
);
}
}
var _color = Colors.black;
var _value = 0.0;
class MyStatelessWidget extends StatefulWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
State<MyStatelessWidget> createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, AppRoutes.Page1);
},
child: Text("Without Argument")),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, AppRoutes.Page1,
arguments: {"name": "lava", "body": "chi"});
},
child: Text("With Argument")),
],
),
);
}
#override
void initState() {}
}
class Page1 extends StatelessWidget {
const Page1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var arguments3 = ModalRoute.of(context)!.settings.arguments;
var arguments2 =
arguments3!=null? arguments3 as Map<String, dynamic>:{};
// {"name": "nodata", "body": "no data"};
return Material(
child: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(arguments2["name"] ?? "Nodata",
style: TextStyle(fontSize: 30)),
Text(
arguments2["body"] ?? "No DAta",
style: TextStyle(fontSize: 30),
),
],
),
),
),
);
}
}
class AppRoutes {
static String failed = "/page2";
static String Page1 = "/page1";
static String home = "/";
}
your design is a little confusing.
if you are trying to get the ID and Quantity in the Cart-screen, then why do you want to navigate to it without the arguments?
any how, I guess you have a use case where you want to do different thing if the arguments are not passed. then the only thing you need is to check if the arguments are null. right?
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
if (routes != null) {
final id = routes['id']; //Received Arguments
final quantity = routes['quantity']; //Received Arguments
final provider =
Provider.of<PopularDishesProvider>(context).getProductById(id);
} else {
// do the things here when no argument is passed.
}
Related
So, I am taking an example from Flutter documentation. Actually, it is working fine but I just want to customize it by passing the result (Map) to parent
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
),
);
}
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Map AdList = {};
callback(AdListNew){
setState(() {
AdList = AdListNew; ////// 1. WANT TO UPDATE THE LIST WITH NEW MAP FROM CHILD ////
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Returning Data Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('This text is'),
SelectionButton(AdList,callback),
],
),
),
);
}
}
class SelectionButton extends StatefulWidget {
Map AdList;
Function(String) callback;
SelectionButton(this.AdList,this.callback);
#override
State<SelectionButton> createState() => _SelectionButtonState();
}
class _SelectionButtonState extends State<SelectionButton> {
String show = 'Pick me';
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (){
NavigateAndDisplay(context);
},
child: Text(show),
);
}
void NavigateAndDisplay(BuildContext context) async{
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
setState(() {
show = result;
});
Map AdListNew = {
'text':result,
};
widget.callback(AdListNew); //// 2. PASS MAP TO PARENT ////
}
}
Here is an error that I got
lib/main.dart:80:21: Error: The argument type 'Map<dynamic, dynamic>'
can't be assigned to the parameter type 'String'.
'Map' is from 'dart:core'.
widget.callback(AdListNew);
^
I really appreciate any answers. Thank you
You are facing this problem because you declared function with input type as String but you are trying to pass Map, so declaring Function with Map will solve your problem.
Map AdList;
Function(Map) callback;
As shown in the image, I'm trying to have a list of dice where I can add or delete a die. I've tried StateProvider, ChangeNotifier, and StateNotifier. Each one doesn't seem to work as I expect it to. I'm trying to make a provider that contains a list of dieWidgets, but I can't figure out how to remove a specific die when I longpress on it. The image shows a popup menu to delete it, that's the long-term goal, but just a longpress delete would be good for now. Thoughts on how to approach this?
Code
main.dart
class DiceNotifier extends ChangeNotifier {
List<DieWidget> dice = [];
void add() {
dice.add(DieWidget());
notifyListeners();
}
void removeDie(int id) {
// FIXME: Unable to delete a die based on id
print(id);
notifyListeners();
}
}
final diceProvider = ChangeNotifierProvider((_) {
return DiceNotifier();
});
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final dice = watch(diceProvider).dice;
return Scaffold(
appBar: AppBar(
title: Text("Dice"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...dice,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read(diceProvider).add();
},
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
die_widget.dart
class DieWidget extends StatefulWidget {
#override
_DieWidgetState createState() => _DieWidgetState();
}
class _DieWidgetState extends State<DieWidget> {
int value = 0;
int id = 0;
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
'$value',
),
onPressed: () {
setState(() {
value++;
id++;
});
// context.read(dieProvider).increment();
},
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDie(id);
// print(this.value);
},
);
}
}
One solution would be to define a parameter value in the DiceWidget class:
class DiceWidget extends StatefulWidget {
const DiceWidget({ Key key, this.value }) : super(key: key);
int value;
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
And access this data from the DiceWidget:
class DiceWidget extends StatefulWidget {
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
class _DiceWidgetState extends State<DiceWidget> {
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
widget.value.toString() ?? '',
),
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDice(widget.value);
// print(widget.value);
},
);
}
}
In the DiceNotifier class, I'd recommend to implement the dices array as a List<int>:
List<int> dices = [];
Therefore, the addDice() and removeDice() functions will be, respectively:
class DiceNotifier extends ChangeNotifier {
List<int> dices = [];
void addDice() {
dices.add(dices.length);
notifyListeners();
}
void removeDice(int id) {
dices.remove(id);
print(id);
notifyListeners();
}
}
To make the example work, we need to modify the MyHomePage Column children as well, to build the list of DiceWidgets:
...dices.map((d) => DiceWidget(value: d)).toList(),
The whole example will then be:
main.dart:
class DiceNotifier extends ChangeNotifier {
List<int> dices = [];
void addDice() {
dices.add(dices.length);
notifyListeners();
}
void removeDice(int id) {
dices.remove(id);
print(id);
notifyListeners();
}
}
final diceProvider = ChangeNotifierProvider((_) {
return DiceNotifier();
});
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final dices = watch(diceProvider).dices;
return Scaffold(
appBar: AppBar(
title: Text("Dice"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...dices.map((d) => DiceWidget(value: d)).toList(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read(diceProvider).addDice();
},
child: Icon(Icons.add),
),
);
}
}
dice_widget.dart:
class DiceWidget extends StatefulWidget {
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
class _DiceWidgetState extends State<DiceWidget> {
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
widget.value.toString() ?? '',
),
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDice(widget.value);
print(widget.value);
},
);
}
}
A simple, but very complicated question: What’s the best way to add a tap to focus functionality for the Flutter camera?
I’ve searched the entire World Wide Web about elegant solutions, but I found nothing.
Do you have an idea?
I might be late but you can try adv_camera package.
Here is a simple example:
import 'package:adv_camera/adv_camera.dart';
import 'package:flutter/material.dart';
class CameraApp extends StatefulWidget {
final String id;
const CameraApp({Key? key, required this.id}) : super(key: key);
#override
_CameraAppState createState() => _CameraAppState();
}
class _CameraAppState extends State<CameraApp> {
List<String> pictureSizes = <String>[];
String? imagePath;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AdvCamera Example'),
),
body: SafeArea(
child: AdvCamera(
initialCameraType: CameraType.rear,
onCameraCreated: _onCameraCreated,
onImageCaptured: (String path) {
if (this.mounted)
setState(() {
imagePath = path;
});
},
cameraPreviewRatio: CameraPreviewRatio.r16_9,
focusRectColor: Colors.purple,
focusRectSize: 200,
),
),
floatingActionButton: FloatingActionButton(
heroTag: "capture",
child: Icon(Icons.camera),
onPressed: () {
cameraController!.captureImage();
},
),
);
}
AdvCameraController? cameraController;
_onCameraCreated(AdvCameraController controller) {
this.cameraController = controller;
this.cameraController!.getPictureSizes().then((pictureSizes) {
setState(() {
this.pictureSizes = pictureSizes ?? <String>[];
});
});
}
}
I have a list of event name in a stateful widget like this
main.dart
class Fav extends StatefulWidget {
#override
_FavState createState() => _FavState();
}
class _FavState extends State<Fav> {
final PageController ctrl = PageController(viewportFraction: 0.8);
final Firestore db = Firestore.instance;
Stream slides;
var fav = ['3-Tech Event'];
.
.
.
And on another page, I want to add a string, let's say,
'5-Art Exhibit'
into the
var fav = ['3-Tech Event'];
to get the final result
fav = ['3-Tech Event', '5-Art Exhibit'];
on the page above. How do I do that? Here's my code for the button
Event.dart
class Star extends StatefulWidget {
#override
_StarState createState() => _StarState();
}
class _StarState extends State<Star> {
Color _iconColor = Colors.grey;
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(55.0),
child: Transform.scale(
scale: 2.0,
child: IconButton(
icon: Icon(
Icons.star,
color: _iconColor,
),
onPressed: () {
setState(() {
_iconColor = (_iconColor == Colors.yellow) ? Colors.grey : Colors.yellow;
});
})
),
);
}
}
Thank you in advance!
UPDATE
I followed #Viren V Varasadiya advice and updated my code to this
main.dart
class Fav extends StatefulWidget {
#override
_FavState createState() => _FavState();
}
class _FavState extends State<Fav> {
var fav = ['3-Tech Event'];
updatedata(String item) {
setState(() {
fav.add(item);
});
}
And on the other file, I removed Star class (because it's intended to be used in another class anyway) and it looked like this
class Event extends StatefulWidget {
final eventInfo;
Event({Key key, List eventInfo}) //I have to pass a list of data to this
: this.eventInfo = eventInfo, //page from another class
super(key: key);
final Function updatedata;
Event.addToFavWith({this.updatedata});
#override
_EventState createState() => _EventState();
}
class _EventState extends State<Event> {
Color _iconColor = Colors.grey;
#override
Widget build(BuildContext context) {
.
.
.
Container( //This used to be Container(child:Star())
child: InkWell(
child: Container(
padding: EdgeInsets.all(55.0),
child: Transform.scale(
scale: 2.0,
child: IconButton(
icon: Icon(
Icons.star,
color: _iconColor,
),
onPressed: () {
setState(() {
_iconColor = (_iconColor == Colors.yellow)
? Colors.grey
: Colors.yellow;
widget.updatedata(name);
});
})),
),
)),
And now I get a couple of errors.
All final variables must be initialized, but 'eventInfo' is not. Try
adding an initializer for the field.
All final variables must be initialized, but 'updatedata' is not. Try
adding an initializer for the field.
You have to create a function in parent widget and pass it to child widget and call it in child widget, we work for you.
Following minimal code help you more.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
var fav = ['3-Tech Event'];
updatedata(String item) {
setState(() {
fav.add(item);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(fav.toString()),
Star(
updatedata: updatedata,
),
],
)));
}
}
class Star extends StatefulWidget {
final Function updatedata;
Star({this.updatedata});
#override
_StarState createState() => _StarState();
}
class _StarState extends State<Star> {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("add item"),
onPressed: () {
widget.updatedata('5-Art Exhibit');
}),
);
}
}
Update:
There is no need to create named constructor.
class Event extends StatefulWidget {
final eventInfo;
Event({Key key, List eventInfo, this.updatedata}) //I have to pass a list of data to this
: this.eventInfo = eventInfo, //page from another class
super(key: key);
final Function updatedata;
#override
_EventState createState() => _EventState();
}
In sample we pass the single model like this
return ScopedModel<CounterModel>(
model: CounterModel(), // only one model here
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
);
How to pass some models instead of single model? So that to use them later this way
Widget build(BuildContext context) {
final username =
ScopedModel.of<UserModel>(context, rebuildOnChange: true).username;
final counter =
ScopedModel.of<CounterModel>(context, rebuildOnChange: true).counter;
return Text(...);
}
You can copy paste and run the full code below.
You can reference this https://newcodingera.com/scoped-model-in-flutter/
You can pass your three models and wrap them nested
Code snippet
void main() {
runApp(MyApp(
counterModel: CounterModel(),
userModel: UserModel('Brian'),
dataModel: DataModel('this is test'),
));
}
class MyApp extends StatelessWidget {
final CounterModel counterModel;
final UserModel userModel;
final DataModel dataModel;
const MyApp({
Key key,
#required this.counterModel,
#required this.userModel,
#required this.dataModel,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<DataModel>(
model: dataModel,
child: ScopedModel<UserModel>(
model: userModel,
child: ScopedModel<CounterModel>(
model: counterModel,
Working demo
Full code
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() {
runApp(MyApp(
counterModel: CounterModel(),
userModel: UserModel('Brian'),
dataModel: DataModel('this is test'),
));
}
class MyApp extends StatelessWidget {
final CounterModel counterModel;
final UserModel userModel;
final DataModel dataModel;
const MyApp({
Key key,
#required this.counterModel,
#required this.userModel,
#required this.dataModel,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<DataModel>(
model: dataModel,
child: ScopedModel<UserModel>(
model: userModel,
child: ScopedModel<CounterModel>(
model: counterModel,
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
),
),
);
}
}
// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
void increment() {
// First, increment the counter
_counter++;
// Then notify all the listeners.
notifyListeners();
}
}
class UserModel extends Model {
String _username;
UserModel(String username) : _username = username;
String get username => _username;
set username(String newName) {
_username = newName;
notifyListeners();
}
}
class DataModel extends Model {
String _data;
DataModel(String data) : _data = data;
String get data => _data;
set data(String newData) {
_data = newData;
notifyListeners();
}
}
class CounterHome extends StatelessWidget {
final String title;
CounterHome(this.title);
#override
Widget build(BuildContext context) {
final counter =
ScopedModel.of<CounterModel>(context, rebuildOnChange: true).counter;
final userModel = ScopedModel.of<UserModel>(context, rebuildOnChange: true);
final dataModel = ScopedModel.of<DataModel>(context, rebuildOnChange: true);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${userModel.username} pushed the button this many times:'),
Text('${dataModel.data} From data model'),
// Create a ScopedModelDescendant. This widget will get the
// CounterModel from the nearest parent ScopedModel<CounterModel>.
// It will hand that CounterModel to our builder method, and
// rebuild any time the CounterModel changes (i.e. after we
// `notifyListeners` in the Model).
Text('$counter', style: Theme.of(context).textTheme.display1),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Change Username'),
onPressed: () {
userModel.username = 'Suzanne';
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Change Data'),
onPressed: () {
dataModel.data = 'data changed';
},
),
)
],
),
),
// Use the ScopedModelDescendant again in order to use the increment
// method from the CounterModel
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return FloatingActionButton(
onPressed: model.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
),
);
}
}