I'm new to Flutter. I'm trying to send multiple data to another screen:
// screen1.dart
..
Expanded(
child: RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => new Screen2(name: thing.name, email: thing.email, address: thing.address, etc..),
),
);
},
),
),
..
// screen2.dart
class Screen2 extends StatefulWidget{
Screen2({this.name}, {this.email}, {this.address}, etc..);
final String name;
final String email;
final String address;
// etc
#override
State<StatefulWidget> createState() { return new Screen2State();}
}
class Screen2State extends State<Screen2> {
Widget build(BuildContext context) {
return new WillPopScope(
..
child: Scaffold(
..
new Row(
children: <Widget>[
new Text(widget.name),
new Text(widget.email),
new Text(widget.address),
etc..
],
),
)
)
}
But I get the error: A non-null String must be provided to a Text widget.
The data is transferred from TextEditingControllers. It works when there is only 1 data transferred, but fails when there are 2 or more.
What's the correct way to send multiple data between screens?
Everything looks fine but you need to change in the Screen 2 class constructor to this
Screen2({this.name, this.email, this.address, etc..});
Modified Code
// screen1.dart
..
Expanded(
child: RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => new Screen2(name: thing.name, email: thing.email, address: thing.address, etc..),
),
);
},
),
),
..
// screen2.dart
class Screen2 extends StatefulWidget{
Screen2({this.name, this.email, this.address, etc..});
final String name;
final String email;
final String address;
// etc
#override
State<StatefulWidget> createState() { return new Screen2State();}
}
class Screen2State extends State<Screen2> {
Widget build(BuildContext context) {
return new WillPopScope(
..
child: Scaffold(
..
new Row(
children: <Widget>[
new Text(widget.name),
new Text(widget.email),
new Text(widget.address),
etc..
],
),
)
)
}
Note: Text Widget will not accept null values so please make sure you are passing all the values. Or you can initialize the variables with the default value to blank
final String name="";
final String email="";
final String address="";
Consider passing the arguments through route arguments. Refer official doc here https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
Related
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.
}
I have 2 classes, one of them requires passing data, and class B does not have data for this class, for example, the login class passes the registration data to class A, but class B does not have this data, but it needs access to class A?
i used Navigation.of(context).pushNamed(context, classB.id)
but not work
you can use constructor but in this case, whenever you use this class, you have to provide value, also you can make class value nullable and check it on build time. Another way is passing data by Route.
for more navigate-with-arguments
Here are is example:
Passing data using ModalRoute
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WidgetA(),
settings: RouteSettings(
arguments: "Data for A",
)),
);
Receive Data
class WidgetA extends StatelessWidget {
static final routeName = "/widgetA";
#override
Widget build(BuildContext context) {
final data = ModalRoute.of(context)!.settings;
late String retriveString;
if (data.arguments == null)
retriveString = "empty";
else
retriveString = data.arguments as String;
return Scaffold(
body: Column(
children: [
Text("Widget A"),
Text("Got data from parent $retriveString"),
],
),
);
}
}
Passing Emptydata using ModalRoute
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WidgetB(),
),
);
On Receiver side
class WidgetB extends StatelessWidget {
static final routeName = "/widgetB";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Text("Widget B"),
],
),
);
}
}
Passing data using Constructor
must provide while using widget.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WidgetC(data: "for C"),
),
);
Receiver
class WidgetC extends StatelessWidget {
final String data;
const WidgetC({Key? key, required this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [Text("Widget C "), Text("data using Constructor: $data")],
),
);
}
}
Passing data(optional) using Constructor
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WidgetD(),
),
);
Receiver
class WidgetD extends StatelessWidget {
final String? data;
WidgetD({Key? key, this.data = ""}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Text("Widget D nullable "),
Text("data using Constructor: $data")
],
),
);
}
}
I'm trying to print my card widget title in the card details page but I'm getting " A non-null String must be provided to a Text widget".
Any suggestion or help on how can I fix this?.
Model.dart
class Item {
int id;
String title;
Item(
{this.id, this.title });
}
CardWidget.dart
import 'package:maxis_mobile/ui/screens/cardDetails-screen.dart';
import 'cardModel.dart';
class _CardWidgetState extends State<CardWidget> {
final item = Item();
#override
Widget build(BuildContext context) {
return Container(
child: SingleChildScrollView(
child: Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CardDetails(item: item), //not sure this is right.
));
},
child: Column(children: [
Container(
child: Row(
children: [
Container(
child: Text(
widget.title, // Card Title
),
),
),
CardDetails.dart
import 'package:flutter/material.dart';
import '../shared/cardModel.dart';
class CardDetails extends StatelessWidget {
final Item item;
CardDetails({Key key, #required this.item}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text(item.title),
);
}
}
DummyData.dart
List<Item> items = [
Item(id: 0, title: '1. US Daily Retail Delivery By Brands'),
]
In _CardWidgetState you have defined an empty object. That object you passed to the CardDetails page through the constructor, and on the CardDetails page you try in Text widget call item.title but it is null (without value). You need populate this object or try with hardcode string.
Same ase here: A non-null String must be provided to a Text widget
The cause is item declared in CardWidget.dart - there is no title, so item.title in CardDetails.dart is null. To fix the error you can add default value for tile field in Item class:
class Item {
int id;
String title;
Item({this.id, this.title = ''}) : assert(title != null);
}
I have a following state full widget. I need to reuse it as it is by just changing two variables id and collectionName. Generally I would extract a widget, but in this case I am modifying variable firstName which wont let me extract the widget.
class IndividualSignupPage1 extends StatefulWidget {
static final id = 'idIndividualSignupPage1';
final collectionName = 'Individual';
#override
_IndividualSignupPage1State createState() => _IndividualSignupPage1State();
}
class _IndividualSignupPage1State extends State<IndividualSignupPage1> {
String firstName;
DateTime birthDate;
final firestoreObj = Firestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: GeneralAppBar(
appBar: AppBar(),
),
body: Container(
child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
TextField(
onChanged: (value) {
this.firstName = value;
},
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: Card(
child: ListTile(
title: Text(
this.birthDate == null
? 'Birthdate'
: '${this.birthDate.year}-${this.birthDate.month}-${this.birthDate.day}',
),
onTap: () {
DatePicker.showDatePicker(
context,
initialDateTime: this.birthDate,
onConfirm: (newDate, listOfIndexes) {
setState(() {
this.birthDate = newDate;
});
},
);
},
),
),
),
],
),
WFullWidthButton(
name: 'Save',
onPressedFunc: () async {
// save all info to firestore db
firestoreObj.collection(widget.collectionName).document('xyz').setData({
'firstName': this.firstName,
'birthDate': this.birthDate,
}, merge: true);
},
),
]),
),
);
}
}
Thanks
You can pass the arguments to the Class IndividualSignupPage1 and then use it in its corresponding state class _IndividualSignupPage1State with the property "widget." like,
// pass the arguments from another class.
class IndividualSignupPage1 extends StatefulWidget {
final String id;
final String collectionName;
IndividualSignupPage1(this.id,this.collectionName);
#override
_IndividualSignupPage1State createState() => _IndividualSignupPage1State();
}
Let say you want to use id and collectionName in its corresponding state class _IndividualSignupPage1State you can access it using "widget" property like,
appBar: AppBar(title: Text(widget.id)),
**OR**
appBar: AppBar(title: Text(widget.collectionName)),
Note: you can only access the widget property inside functions/methods only.
Create IndividualSignupPage1 constructor and pass data with constructor arguments.
class IndividualSignupPage1 extends StatefulWidget {
final String id;
final String collectionName;
IndividualSignupPage1(this.id,this.collectionName);
I´m trying to send data with pushName. Then i try to get this data to show in a Toast message.
PushName
Navigator.pushNamed(
context,
'/navigator',
arguments: <String, String>{
'instalation': widget.instalation,
'message': DemoLocalizations.of(context)
.text('cancel-message') +
" " +
widget.datameterValue.toString(),
},
);
Trying to retrieve data
class Navigation extends StatefulWidget {
final ConnectionPage args;
Navigation({Key key, this.message, this.instalation, this.args}) : super(key: key);
}
class _NavigationState extends State<Navigation> {
void initState() {
super.initState();
print(widget.args); //NULL
final snackBar = SnackBar(
duration: Duration(seconds: 5),
content: Text(widget.args.messsage+ '.', textAlign: TextAlign.center),
backgroundColor: Colors.red[700],
);
key.currentState.showSnackBar(snackBar);
}
}
The problem: Return null.
So: What is the right way to get data using pushName? In the documentation show how can we get data inside Scaffold but i need to get data in the initState.
UPDATE
Routes
routes: {
'/login': (context) => LoginPage(),
'/navigator': (context) => Navigation(),
'/home': (context) => HomePageScreen(),
'/connect': (context) => ConnectionPage(),
},
UPDATE 2
I try something like this
Navigator.pushNamed(
context,
'/navigator',
arguments: Navigation(
instalation: widget.instalation,
message: DemoLocalizations.of(context)
.text('cancel-message') +
" " +
widget.datameterValue.toString(),
),
);
To do this in initState You need WidgetsBinding.instance.addPostFrameCallback and ModalRoute.of(context).settings.arguments
Demo pass arguments: {'instalation': "123", "message": "456"}
You can see full code and working demo picture below
code snippet use push
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExtractArgumentsScreen(),
// Pass the arguments as part of the RouteSettings. The
// ExtractArgumentScreen reads the arguments from these
// settings.
settings: RouteSettings(
arguments: {'instalation': "123", "message": "456"},
),
),
);
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final routeArgs1 =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final instalation = routeArgs1['instalation'];
final message = routeArgs1['message'];
print('instalation ${instalation}');
print('message ${message}');
key.currentState
.showSnackBar(SnackBar(content: Text(message)));
});
}
code snippet use Navigator.pushNamed
return MaterialApp(
// Provide a function to handle named routes. Use this function to
// identify the named route being pushed, and create the correct
// Screen.
routes: {
'/extractArguments': (context) => ExtractArgumentsScreen(),
},
...
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: {'instalation': "123", "message": "456"},
);
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// Provide a function to handle named routes. Use this function to
// identify the named route being pushed, and create the correct
// Screen.
routes: {
'/extractArguments': (context) => ExtractArgumentsScreen(),
},
onGenerateRoute: (settings) {
// If you push the PassArguments route
if (settings.name == PassArgumentsScreen.routeName) {
// Cast the arguments to the correct type: ScreenArguments.
final ScreenArguments args = settings.arguments;
// Then, extract the required data from the arguments and
// pass the data to the correct screen.
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
},
title: 'Navigation with Arguments',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// A button that navigates to a named route that. The named route
// extracts the arguments by itself.
RaisedButton(
child: Text("Navigate to screen that extracts arguments"),
onPressed: () {
// When the user taps the button, navigate to the specific route
// and provide the arguments as part of the RouteSettings.
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: {'instalation': "123", "message": "456"},
);
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExtractArgumentsScreen(),
// Pass the arguments as part of the RouteSettings. The
// ExtractArgumentScreen reads the arguments from these
// settings.
settings: RouteSettings(
arguments: {'instalation': "123", "message": "456"},
),
),
);*/
},
),
// A button that navigates to a named route. For this route, extract
// the arguments in the onGenerateRoute function and pass them
// to the screen.
RaisedButton(
child: Text("Navigate to a named that accepts arguments"),
onPressed: () {
// When the user taps the button, navigate to a named route
// and provide the arguments as an optional parameter.
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'Accept Arguments Screen',
'This message is extracted in the onGenerateRoute function.',
),
);
},
),
],
),
),
);
}
}
// A Widget that extracts the necessary arguments from the ModalRoute.
class ExtractArgumentsScreen extends StatefulWidget {
static const routeName = '/extractArguments';
#override
_ExtractArgumentsScreenState createState() => _ExtractArgumentsScreenState();
}
class _ExtractArgumentsScreenState extends State<ExtractArgumentsScreen> {
final GlobalKey<ScaffoldState> key = new GlobalKey<ScaffoldState>();
final snackBar = SnackBar(
duration: Duration(seconds: 5),
content: Text("message" + '.', textAlign: TextAlign.center),
backgroundColor: Colors.red[700],
);
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final routeArgs1 =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final instalation = routeArgs1['instalation'];
final message = routeArgs1['message'];
print('instalation ${instalation}');
print('message ${message}');
key.currentState
.showSnackBar(SnackBar(content: Text(message)));
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
// Extract the arguments from the current ModalRoute settings and cast
// them as ScreenArguments.
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final instalation = routeArgs['instalation'];
final message = routeArgs['message'];
return Scaffold(
key: key,
appBar: AppBar(
title: Text(' ${routeArgs['code']} '),
),
body: Column(
children: <Widget>[
Center(
child: Text('instalation ${instalation}'),
),
RaisedButton(
onPressed: () {
key.currentState.showSnackBar(snackBar);
},
),
],
),
);
}
}
// A Widget that accepts the necessary arguments via the constructor.
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
// This Widget accepts the arguments as constructor parameters. It does not
// extract the arguments from the ModalRoute.
//
// The arguments are extracted by the onGenerateRoute function provided to the
// MaterialApp widget.
const PassArgumentsScreen({
Key key,
#required this.title,
#required this.message,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(message),
),
);
}
}
// You can pass any object to the arguments parameter. In this example,
// create a class that contains both a customizable title and message.
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
So, I see you're using the simple routes approach.
In order to extract route arguments you need to supply an onGenerateRoute function to your MaterialApp (or Cupertino, I guess).
You can find an exhaustive example on how to do it here, so I won't crowd this answer more than that.
Hope this solves your problem, happy coding!