In the tutorial, String categoryTitle; and List displayedMeals; were working fine. In the current version of flutter it won't allow me to just declare as it is. What should I initialize them to? It would be nice to know what is the equivalent in declaring String and List now since thre is a null safety. Also, when I put String? and ListMeal? as declaration and put [!], i get an error of null check operator used on a null value. I can't put late also because I don't what should I initialize these declarations.
import 'package:flutter/material.dart';
import '../widgets/meal_item.dart';
import '../dummy_data.dart';
import '../models/meal.dart';
class CategoryMealsScreen extends StatefulWidget {
static const routeName = '/category-meals';
#override
State<CategoryMealsScreen> createState() => _CategoryMealsScreenState();
}
class _CategoryMealsScreenState extends State<CategoryMealsScreen> {
// final String categoryId;
String categoryTitle;
List<Meal> displayedMeals;
var _loadedInitData = false;
#override
void initState() {
super.initState();
}
#override
void didChangeDependecies() {
if (!_loadedInitData) {
final routeArgs =
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
categoryTitle = routeArgs['title']!;
final categoryId = routeArgs['id'];
displayedMeals = DUMMY_MEALS.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
}
_loadedInitData = true;
super.didChangeDependencies();
}
void _removeMeal(String mealId) {
setState(() {
displayedMeals!.removeWhere((meal) => meal.id == mealId);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(categoryTitle!),
),
body: ListView.builder(
itemBuilder: (ctx, index) {
return MealItem(
id: displayedMeals[index].id,
title: displayedMeals[index].title,
imageUrl: displayedMeals[index].imageUrl,
duration: displayedMeals[index].duration,
affordability: displayedMeals[index].affordability,
complexity: displayedMeals[index].complexity,
removedItem: _removeMeal,
);
},
itemCount: displayedMeals.length,
),
);
}
}
It happens because of null safety. Either you declare your variables as nullable, so they can be null or you use the keyword late in front of the variable like this:
late String categoryTitle;
late List<Meal> displayedMeals;
But then you should also declare the variables in the initState or before you use them, otherwise an exception will be thrown.
Write like that :
String? categoryTitle;
its nullsafety
If I pass some argument while rotating from one page to another page, how can I use the argument value at the new page initState in Flutter?
Assume I have two Flutter page, and I would like to navigate to another pages as following:
Navigator.pushNamed(
context,
'/page2',
arguments: {
'name': widget.name,
"id": widget.id,
},
);
Then I can display the argument at the page2 by replacing the following code inside the build function:
final Map<String, dynamic>? args =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
var name= args!["name"];
var id = args["id"];
print("name:" + name);
print("id" + id);
Now, I would like to do some API call by passing the arguments to the initState.
#override
void initState() {
super.initState();
// how can I pass the argument id and argument name to the function?
callApi(id, name);
}
callApi(id, name) async {
var response = await api(id, name);
}
You should define the routes into your app main.dart file like this.
MaterialApp(
routes: {
"/page1": (context) => const Homepage(),
"/page2": (context) => SecondPage(
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>),
},
initialRoute: "/page1",
)
And then define the parameters into the second page class
class SecondPage extends StatefulWidget {
final Map<String, dynamic>? args;
const SecondPage(this.args, {Key? key}) : super(key: key);
#override
SecondPageState createState() => SecondPageState();
}
And can use it into the init state like this.
class SecondPageState extends State<SecondPage> {
#override
void initState() {
callApi(widget.args["name"], widget.args["id"]);
super.initState();
}
}
I've a question:
In my Widget build(BuildContext context), I want to store a certain value,
final userName = book.owner
(book is the reference to the certain value from Firestore)
But it's done not in the right way to my lack of knowledge. I'd appreciate if someone could guide through that.
Thank you in advance!
Snippet of my code
class BookView extends StatefulWidget {
final Book book;
BookView({Key key, #required this.book}) : super(key: key);
DatabaseMethods databaseMethods = new DatabaseMethods();
var userName;
#override
_BookViewState createState() => _BookViewState(book);
}
class _BookViewState extends State<BookView> {
Book book;
_BookViewState(this.book);
String userName;
#override
void initState() {
userName = book.owner;
super.initState();
}
// final Book book;
createChatroomAndStartConversation({var userName}) {
if (userName != Constants.myName) {
String roomId = getChatRoomId(userName, Constants.myName);
List<String> users = [userName, Constants.myName];
Map<String, dynamic> chatRoomMap = {
"Users": users,
"roomId": roomId,
};
DatabaseMethods().createChatRoom(roomId, chatRoomMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(roomId, userName)),
);
} else {
print("You cannot send msg to your self");
}
}
#override
Widget build(BuildContext context) {
//widget.book;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
...
FlatButton(
child: Text(
"Get contact with",
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
onPressed: () {
createChatroomAndStartConversation(
userName: userName);
...
}
Snippet of Value not in range: 1
getChatRoomId(String a, String b) {
if (a.substring(0, 1).codeUnitAt(0) > b.substring(0, 1).codeUnitAt(0)) {
return "$b\_$a";
} else {
return "$a\_$b";
}
}
It's not a good practice to store any data in build() method, because this method is invoked too many times to do the such kind of move. Consider using StatefulWidget to store any state you have in the widget, for the very beginning. When you use this widget, you can define this all in such way:
class YourWidget extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String userName;
#override
void initState() {
userName = book.owner;
super.initState()
}
#override
Widget build(BuildContext context) {
return Container(child: Text(userName),);
}
}
Here, in initState() you can retrieve value from book and set it to userName. But for more complex and bigger applications, consider using StateManagement solutions and some kind of architectural patterns i.e. Riverpod, Provider, MobX, BLoC.. Because changing the state via setState() method will cause rebuilding whole child widget tree, which could freeze whole UI in complex app.
UPD to 'Snippet of my code':
According to your code, if you are using a 'book' from Widget, not its state - use widget.book, in such way you have access to widget members, because of this you don't need a constructor of state. So, due to these changes, your code might looks like:
class BookView extends StatefulWidget {
final Book book;
BookView({Key key, #required this.book}) : super(key: key);
// You DON'T need this here, because you are retrieving these methods
// inside your state via DatabaseMethods constructor
DatabaseMethods databaseMethods = DatabaseMethods();
#override
_BookViewState createState() => _BookViewState(book);
}
class _BookViewState extends State<BookView> {
String userName;
#override
void initState() {
// Using widget.book to retrieve Book object from state's widget
userName = widget.book.owner;
super.initState();
}
createChatroomAndStartConversation({var userName}) {
if (userName != Constants.myName) {
String roomId = getChatRoomId(userName, Constants.myName);
// Also, it's just a recommendation, try to omit local variables types
// because they are already known with List type (String). Also, this
// all is about chatRoomMap
var users = <String>[userName, Constants.myName];
final chatRoomMap = <String, dynamic>{
"Users": users,
"roomId": roomId,
};
DatabaseMethods().createChatRoom(roomId, chatRoomMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(roomId, userName)),
);
} else {
print("You cannot send msg to your self");
}
}
#override
Widget build(BuildContext context) {
// your widgets here
}
}
UPD 2:
Second trouble and issue with 'Snippet of Value not in range: 1'. I could to reproduce it with given value of 'a' as empty string. So, your function invocation is like getChatRoomId('', 'user123'), because of empty 'userName', substring function can't take values from range [0, 1), so exception is raised.
I am passing value between 2 screens I need to know how can I simply print value?
This is how I am sending value
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ViewPostScreen(
id: id,
),
),
);
},
This is my second page
class ViewPostScreen extends StatefulWidget {
final int id;
ViewPostScreen({Key key, #required this.id}) : super(key: key);
#override
_ViewPostScreenState createState() => _ViewPostScreenState();
}
class _ViewPostScreenState extends State<ViewPostScreen> {
}
I need to print the value of id in _ViewPostScreenState I try with simple print but showing error anyone can help?
The problem is you are not using print inside a method rather at the class level. Create a method and then use print inside it.
void method() {
print(...);
}
Full solution:
class ViewPostScreen extends StatefulWidget {
final int id;
ViewPostScreen({Key key, #required this.id}) : super(key: key);
#override
_ViewPostScreenState createState() => _ViewPostScreenState();
}
class _ViewPostScreenState extends State<ViewPostScreen> {
void method() {
print(widget.id);
}
}
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) {
print(id); // print here
return ViewPostScreen(
id: id,
);
}
),
);
},
You can access the widget's attributes from the State using widget
print(widget.id.toString());
You cannot call the print function in the class body. It needs to be within a function. You can use initState as it is the first function that runs.
void initState() {
super.initState();
print(widget.id.toString());
}
Note that you will also need a build method in your State class
Might have been asked before but I can't find it but how do you pass a arguments to a named route?
This is how I build my routes
Widget build(BuildContext context) {
return new Navigator(
initialRoute: 'home/chooseroom',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'home/chooseroom':
// navigates to 'signup/choose_credentials'.
builder = (BuildContext _) => new ChoosePage();
break;
case 'home/createpage':
builder = (BuildContext _) => new CreateRoomPage();
break;
case 'home/presentation':
builder = (BuildContext _) => new Presentation();
break;
default:
throw new Exception('Invalid route: ${settings.name}');
}
return new MaterialPageRoute(builder: builder, settings: settings);
},
);
This is how you call it
Navigator.of(context).pushNamed('home/presentation')
But what if my widget is new Presentation(arg1, arg2, arg3)?
No need for onGenerateRoute. Simply use
var exampleArgument = 'example string';
Navigator.pushNamed(
context,
'/otherscreen',
arguments: {'exampleArgument': exampleArgument},
);
and extract the arguments as follows:
#override
Widget build(BuildContext context) {
final arguments = (ModalRoute.of(context)?.settings.arguments ?? <String, dynamic>{}) as Map;
print(arguments['exampleArgument']);
return Scaffold(...);
}
pushNamed() now supports arguments as of this merged pull request. If you can't wait, switch to channel master (flutter channel master and probably followed by flutter upgrade).
How to send:
Navigator.pushNamed(ctx, '/foo', arguments: someObject);
How to receive:
...
return MaterialApp(
...
onGenerateRoute: _getRoute,
...
);
...
Route<dynamic> _getRoute(RouteSettings settings) {
if (settings.name == '/foo') {
// FooRoute constructor expects SomeObject
return _buildRoute(settings, new FooRoute(settings.arguments));
}
return null;
}
MaterialPageRoute _buildRoute(RouteSettings settings, Widget builder) {
return new MaterialPageRoute(
settings: settings,
builder: (ctx) => builder,
);
}
The "arguments" can be any object, e.g. a map.
It took me a while to notice this, as I'm a newbie to Flutter. But the arguments you add using Navigator.pushNamed get sent directly to the widget you pushed NOT the MaterialApp for routing.
So in widget you push a new screen from you'll have:
Navigator.pushNamed(
context,
SomePage.routeName,
arguments: {
'v1': 'data1',
'v2': 'data2',
'v3': 'data3',
},
)
You won't need those arguments in your constructor at all. Instead your pull them out in the SomePage widget like the others are saying; namely via:
final arg = ModalRoute.of(context)!.settings.arguments as Map;
and can assign them within SomePage build like:
randomVar1 = arg['v1'];
randomVar2 = arg['v2'];
randomVar3 = arg['v3'];
using whatever keys you put in.
if you want MaterialApp to handle it then you use the onGenerateRoute method. This took me forever to notice that the arguments go directly to the widget pushed. For me it was counter-intuitive.
Arguments can be any object, you can make an array as you can see:
Navigator.of(context).pushNamed('/upload', arguments: {'_imagePath':_imagePath,
'num_docfiscal':num_docfiscal,'dta_docfiscal':dta_docfiscal});
and access to the router class.
Basically you have 2 options :
Use some 3rd party package for routing - I think the best is Fluro .
exploit onGenerateRoute . This option is limited to args you can pass (string/numbers)
To use second option, assuming you want to pass three arguments: Navigator.of(context).pushNamed('home/presentation:arg1:1337:hello')
MaterialApp (
... ,
onGenerateRoute: handleRoute,
routes:... , )
Route<dynamic> handleRoute(RouteSettings settings) {
WidgetBuilder builder;
final List<String> uri = settings.name.split('/');
if (uri[0].startsWith('home')) {
// handle all home routes:
if(uri[1].startsWith('presentation:'){
// cut slice by slice
final String allArgs =
uri[1].substring('presentation:'.length);
final List<String> args = allArgs.split(':');
// use your string args
print(args[0]); // prints "arg1"
int x = int.parse(args[1]); // becomes 1337
print(args[2]); // prints "hello"
builder = (ctx)=> Presentation(args[0],args[1],args[2]);
...
For simple navigation with arguments from WidgetA to WidgetB
Define routes in MaterialApp main widget:
return MaterialApp(
routes: {
'/routeAB': (context) => WidgetB(),
},
In WidgetA use pushNamed method to navigate to WidgetB:
onTap: () {
Navigator.pushNamed(context, '/routeAB',
arguments: {
'arg1': val1,
'arg2': val2,
...
Get arguments in WidgetB:
Map args = ModalRoute.of(context).settings.arguments;
Note: new routing options may be available soon.
Could this be a possible option?
Use push with a RouteSettings argument specifying the named route.
This way you can directly pass arguments of any type (including objects) to your destination Widget in a type safe manner and skip using arguments. You won't need to create a single-use throwaway arguments class nor a Map.
RouteSettings in push can supply a named Route to your Navigation stack which you can search for / use in future routing decisions, just the same as if you had used pushNamed.
Push + RouteSettings
To use push with a named route, use RouteSettings argument with the route name.
Example: a user logs in on Page1 and now you want push them from Page1 to Page2
Directly inside Page1 pass the User object (loggedInUser) to Page2 within a Navigator.push call and use a RouteSettings arg with your route name (/page2).
Navigator.push(context, MaterialPageRoute(
builder: (context) => Page2(user: loggedInUser),
settings: RouteSettings(name: '/page2')
));
And in Page2 widget, you can expect and use the User object directly.
class Page2 extends StatefulWidget {
final User loggedInUser;
Page2(this.loggedInUser);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
User loggedInUser;
#override
void initState() {
super.initState();
loggedInUser = widget.loggedInUser;
print(loggedInUser.name);
}
#override
Widget build(BuildContext context) {
}
}
Later on you can use /page2 route name. For example if you're at /page3 and you want to popUntil(context, ModalRoute.withName('/page2')), this allows that.
Under the Hood
Flutter's Navigator class shows pushNamed uses push + routeNamed and routeNamed uses RouteSettings.
Future<T> pushNamed<T extends Object>(
String routeName, {
Object arguments,
}) {
return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
Route<T> _routeNamed<T>(String name, { #required Object arguments, bool allowNull = false }) {
final RouteSettings settings = RouteSettings(
name: name,
arguments: arguments,
);
Route<T> route = widget.onGenerateRoute(settings) as Route<T>;
return route;
Pass arguments:
Navigator.pushNamed(YourScreen.routeName, arguments: {"title":myTitle, "user_name":userName});
Extract arguments:
Map<String, dynamic> arguments = new Map<String, dynamic>.from(settings.arguments);
page = MyRecordingScreen(title: arguments["title"], tags: arguments["user_name"], );
For named Routes with multiple arguments or dynamic object you need to do as follow(this is MVVM pattern example):
navigator:
void navigateEditParty(int index) {
_navigationService.navigateTo(PartyEditRoute,
arguments: {"hunter": hunter, "index": index});
}
router:
case PartyEditRoute:
Map args = settings.arguments;
return _getPageRoute(
routeName: settings.name,
viewToShow: PartyEditView(
hunter: args["hunter"],
index: args["index"],
),
);
class:
PartyEditView({Key key, this.hunter, this.index}) : super(key: key);
It is always best to wrap your arguments in an object to avoid error prone code.
Below are the working example on how you can achieve it in flutter dart.
To send
Navigator.of(context).pushNamed(Routes.submitSuccess, arguments: successModel);
To Receive
case Routes.submitSuccess:
if (settings.arguments is SubmitSuccessModel) {//Check for instance here
return CupertinoPageRoute(
builder: (_) =>
SubmitSuccessPage(successModel: settings.arguments));
}
Model Object
class SubmitSuccessModel {
SubmitSuccessModel(
{this.title, this.desc, this.actionButtonName, this.widget});
String title;
String desc;
String actionButtonName;
Widget widget;
}
final SubmitSuccessModel successModel = SubmitSuccessModel(
title: 'Title',
desc: 'Desc',
actionButtonName: 'Done',
);
It took me a while to notice this, as I'm a newbie to Flutter. But the arguments you add using Navigator.pushNamed get sent directly to the widget you pushed NOT the MaterialApp for routing.
You don't need those arguments in your constructor at all. Instead your pull them out in the widget like the others are saying; namely via:
final arg = ModalRoute.of(context)!.settings.arguments as Map;
and can assign them like:
randomVar1 = arg['v1'];
randomVar2 = arg['v2'];
randomVar3 = arg['v3'];
using whatever keys you put in.
if you want MaterialApp to handle it then you use the onGenerateRoute method. This took me forever to notice that the arguments go directly to the widget pushed. For me it was counter-intuitive.
screeen_1.dart
Navigator.of(context).pushNamed('/screen2',arguments: {'var1': val1 ,'var2': val2, "var3": val3 ,"var4" : val4}); //sending of the values to route_generator.dart
route_generator.dart
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) //from screeen_1.dart
{
// Getting arguments passed in while calling Navigator.pushNamed
Map args = settings.arguments;
switch (settings.name) {
case '/screen2':
if (args is Map) {
return MaterialPageRoute(
builder: (_) => screen_2(args),
);
}
return _errorRoute(); //optional written in a scaffold when any error arrises
default:
return _errorRoute(); //optional written in a scaffold when any error arrises
}
screen_2.dart
screen_2(Map args) //follow the params from the route_generator.dart
{
return Scaffold(
body: FutureBuilder(
future: _screen_2EndpointProvider.fetchScreen_2(args['var1'], args["var2"], args['var3'], args['var4']), //API call inside which it called the args
builder: (context, snapshot) {
return snapshot.hasData
? screen_2(param: snapshot.data)
: Center(child: CircularProgressIndicator());
}),
);
}
I tried to provide a simple example, if it could be helpful to anyone, because i was facing the same error after a lots of trials it got solved.
After a long day fighting with the routes and onGeneratedRoutes on how i should pass the data and retrieve i finally understand it here is what i learned about
routes: - the property of MaterialApp
routes: {'/profile': (context) => const Profile()}
onTap () {
Navigator.pushNamed(context, '/profile', arguments: {'name': 'someone'});
};
On the profile Screen
final args = ModalRoute.of(context)!.settings.arguments as Map;
don't forget do Downcast to the right data type using the as keyword.
useGeneratedRoute: - the property of MaterialApp for dynamic routes
onGenerateRoute: Routes.RouteGenerator,
on Routes class
static Route<dynamic> generateR(RouteSettings settings) {
switch (settings.name) {
final args = settings.arguments;
case 'profile':
if (args is Map) {
return MaterialPageRoute(
builder: (context) => Profile(
title: args,
));
}
default:
return MaterialPageRoute(
builder: ((context) => const NotFound()));
}
}
on class Profile create a constructor and a field of type Map or whatever you what to pass as data.
final Map<String> data;
const Profile({
super.key,
required this.data,
});
and you can now use the data as you wish.
child: const Text(data[name]);
So use the best option for your context i struggle so much to understand because i was get the null error. hope it's help you need
That's how to send parameters / arguments-
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewPresentation(
id: '101',
name: 'Unnamed',
)));
},
child: const Text('Submit'))
And that's how to receive -
class NewPresentation extends StatefulWidget {
final String id, name;
const NewPresentation({Key? key, required this.id, required this.name}) : super(key: key);
#override
State<NewPresentation> createState() => _NewPresentationState();
}
class _NewPresentationState extends State<NewPresentation> {
Widget build(BuildContext context) {
return Text(widget.id + widget.name);
}
}
Screen 1 :
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(arguments: e),
builder: (context) =>
const AnnouncementDetail()),
);
screen 2 :
//get parameters
final arg = ModalRoute.of(context)!.settings.arguments as Announcement;
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Anang', style: TextStyle(fontWeight: FontWeight.bold),),
Text('Diposting pada ${DateFormat("d MMMM yyyy", "id_ID")
.format(DateTime.parse('${arg.announcementDate!} 00:00:00.000'))}', style: const TextStyle(fontWeight: FontWeight.normal)),
const SizedBox(height: 16),
Text(arg.announcementTitle!, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),),
const SizedBox(height: 16),
Html(
style : {
"body": Style(
padding: EdgeInsets.zero,
margin: Margins(
bottom: Margin.zero(),
left: Margin.zero(),
top: Margin.zero(),
right: Margin.zero(),
)
)
},
data: arg.announcement)
],
)
Announcement.dart
class Announcement {
Announcement({
this.id,
this.announcementDate,
this.announcementTitle,
this.announcement,
this.month,
this.createdBy,
this.createdAt,
this.updatedAt,});
Announcement.fromJson(dynamic json) {
id = json['id'];
announcementDate = json['announcement_date'];
announcementTitle = json['announcement_title'];
announcement = json['announcement'];
month = json['month'];
createdBy = json['created_by'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
int? id;
String? announcementDate;
String? announcementTitle;
String? announcement;
String? month;
int? createdBy;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['announcement_date'] = announcementDate;
map['announcement_title'] = announcementTitle;
map['announcement'] = announcement;
map['month'] = month;
map['created_by'] = createdBy;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}
// Main Screen from where we want to Navigate
Navigator.pushNamed(
context,
"/ScreenName",
arguments: {
'id': "20"
});
I also faced the same issue I was using NavigationRoutes
class NavigationRoutes {
static Route<dynamic> generateScreenRoute(RouteSettings settings) {
// TODO below we are able to get Arguments list
Map args = settings.arguments;
switch (settings.name) {
case "Screen Name":
return MaterialPageRoute(builder: (context) => ScreenName(args));
// TODO pass above argument here and get in the form of Constructor in Screen
// Name Class
}
}