How to add multiple children to flutter body - flutter

Good day, I need help adding a second child to the body of my screen. I keep on getting the error "The argument for the named parameter 'children' was already specified." If I take that piece of code out, my 'app' works perfectly. I've tried adding Column to my body (saw it in a different question) but it still gives me the error.
The problematic code is
,children: [TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenDos(title: 'Feature Screen dos');
Full Code:
class DashBoard extends StatelessWidget {
const DashBoard({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenUno(title: 'Feature Screen uno');
}));
},
child: const Text('Feature Screen uno')
)
]
,children: [TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenDos(title: 'Feature Screen dos');
}));
}
,child: const Text('Feature Screen dos'),
),
]
)
);
}
}`

adding multiple children to Column or Row or any other multi child widget is not like that,
you would have a single Column with a list of children inside it
Column(
children: [
const Text('Child 1'),
const Text('Child 2'),
...
]
)
please refer to Column widget to know more about column, same work goes to row & some other multi child widgets

class DashBoard extends StatelessWidget {
const DashBoard({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
///child one
TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenUno(title: 'Feature Screen uno');
}));
},
child: const Text('Feature Screen uno')
),
///child two
TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenDos(title: 'Feature Screen dos');
}));
}
,child: const Text('Feature Screen dos'),
),
]
)
);
}
}`
body will always take one child we customize further ourselfs by adding column and rows for example here you can add a row as well in children if you want to show something horizontal after text buttons and further add more children to rows

Related

MaterialPageRoute not opening new screen

I am trying to navigate to a new screen when a PopupMenuItem is tapped. I am using showMenu() to create the popup menu.
The code calling the new screen looks like this:-
items: [
PopupMenuItem(
onTap: () {
print(movie.title);
// Navigator.of(context).push(MaterialPageRoute(
// builder: (context) => MoreInfo(movie: movie)));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MoreInfo(movie: movie)));
},
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MoreInfo(movie: movie)));
},
child: Text("More info")),
and MoreInfo screen code looks like this:-
Scaffold(
backgroundColor: Colors.indigo,
appBar: AppBar(
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back_outlined),
),
title: Text(widget.movie.title),
),
However, using the onTap field of PopupMenuItem does not open a new screen. It is printing out movie.title though. I also have the text of the menu item wrapped with an InkWell, when I do click on the text, a new screen does show up, but there is no content on it. Just an empty screen with Colors.indigo background color as specified in MoreInfo, but with none of the other elements such as AppBar.
What could be going wrong here? Do I need to use another type of Navigation?
this piece of code works as well:
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
showMenu(
context: context,
position: const RelativeRect.fromLTRB(0, 0, 0, 0),
items: [
PopupMenuItem(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MoreInfo()));
},
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MoreInfo()));
},
child: const Text("More info")),
)
]);
},
child: const Text('push me'),
)
],
),
),
);
}
}
class MoreInfo extends StatefulWidget {
MoreInfo({Key? key}) : super(key: key);
#override
State<MoreInfo> createState() => _MoreInfoState();
}
class _MoreInfoState extends State<MoreInfo> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: const Icon(Icons.arrow_back_outlined),
),
title: const Text('widget.movie.title'),
),
body: const Center(
child: Text('hi'),
),
);
}
}

Flutter pass provider as an argument

I have a TemplateView page that has a content parameter that hosts content that changes depending on my application.
Each content has a specific provider.
On the other hand, my TemplateView page has a button which calls a validation function common to each provider.
Here the example of my app (in green my TemplateView, in red the content who change):
Here is a simplified code of Template View. We see the call to the content and the validation button which calls the provider of the content ContentView1.
class TemplateView extends StatelessWidget{
final String title;
final StatelessWidget content;
TemplateView ({
Key? key,
required this.title,
required this.content,
required this.validationMessage,
}) : super(key: key);
#override
Widget build(BuildContext context)
{
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body : _buildBody(context),
),
),
);
}
Widget _buildBody(BuildContext context)
{
// Here the call of my provider for the ContentView1
var _messageProvider = Provider.of<ContentView1Provider>(context);
return Column(
children: [
SingleChildScrollView(
child: Container(
child: content,
),
),
InkWell(
child: Container(
child: Text('SAVE'),
),
onTap: () => _messageProvider.validation()
),
],
);
}
}
And here, how I call the TemplateView in my router:
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
title: "Content 1 page",
message: ContentView1(),
),
)
);
All are working, now like I said the contents will change but my TemplateView is common. I therefore cannot enter in the TemplateView the call to the provider directly since it will change depending on the pages.
So I want to make the call to the provider in the TemplateView settings but it doesn't work.
My new TemplateView:
class TemplateView extends StatelessWidget{
final String title;
final StatelessWidget content;
final Function validationMessage; // => I added this line
TemplateView({
Key? key,
required this.title,
required this.content,
required this.validationMessage, // => I added this line
}) : super(key: key);
#override
Widget build(BuildContext context)
{
// => I remove the call of the provider line
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body : _buildBody(context),
),
),
);
}
Widget _buildBody(BuildContext context)
{
return Column(
children: [
SingleChildScrollView(
child: Container(
child: content,
),
),
InkWell(
child: Container(
child: Text('SAVE'),
),
onTap: () => validationMessage() // => I changed this line
),
],
);
}
}
My new router :
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
title: "Content 1 page",
message: Content1View(),
validationMessage: () => Provider.of<ContentView1Provider>(context).validation(),
),
)
);
It doesn't work, how to do this ?
EDIT with the solution
I added Consumer in my router :
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
title: "Content 1 page",
message: Consumer<ContentView1Provider>(builder :(ctx , provider , child){
return ContentView1();
}),
validationMessage: () => Provider.of<ContentView1Provider>(context).validation(),
),
)
);
I am not sure if I understand this case well, but I just tell an Idea if it is work and if I understood what you ask :
in your route pass provider to the Tamplate page :
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
provider : ContentView1Provider , // add this line
title: "Content 1 page",
message: Content1View(),
validationMessage: () => Provider.of<ContentView1Provider>(context).saveMessage(),
),
)
);
in template view recieve this provider :
class TemplateView extends StatelessWidget{
final provider; // add this line
final String title;
final StatelessWidget content;
final Function validationMessage; // => I added this line
TemplateView({
Key? key,
required this.provider, // add this line
required this.title,
required this.content,
required this.validationMessage, // => I added this line
}) : super(key: key);
now You can use Consumer with provider you recieved for each content :
return Consumer<provider>(builder :(ctx , provider , child){
return //what you want ....;
})
May I have missunderstood

Flutter - Navigate in PopupMenuButton onTap does not work

I used to use IconButton's onpressed to navigate to the settings page from my AppBar which worked.
Now I am trying to trigger the navigation from a PopupMenuItem's onTap but the page does not navigate. Both widgets are in the same hierarchy and I can't find out the reason for the different behavior. No error is thrown.
Here is the code of my appBar which contains the actions:
Widget build(BuildContext ctx) {
return Container(
child: Scaffold(
appBar: AppBar(
title: const Text('MyApp'),
actions: [
PopupMenuButton(
itemBuilder: (context) => [
// THE NAVIGATION IN onTap DOES NOT WORK
PopupMenuItem(
child: Text(AppLocalizations.of(context).settings),
onTap: () => _openSettings(ctx),
),
],
icon: Icon(
Icons.settings,
),
),
// THIS WORKS
IconButton(onPressed: () => _openSettings(ctx),
icon: Icon(Icons.settings))
],
),
body: Text("")
),
);
}
And here the function whose navigation call only works inside IconButton's onpressed.
I could confirm that the function was triggered in both cases though:
Future<void> _openSettings(BuildContext ctx) async {
print('settings');
await Navigator.push(
ctx, MaterialPageRoute(builder: (ctx) => SettingsPage()));
print('settings completed');
}
I'd appreciate any help!
Workaround:
I have not found out what the issue with onTap navigation is but now I am just using onSelected which results in the same UX and works:
Widget build(BuildContext ctx) {
return Container(
child: Scaffold(
appBar: AppBar(
title: const Text('MyApp'),
actions: [
PopupMenuButton(
onSelected: (result){
switch(result){
case 0: _openSettings(); break;
case 1: _signOut(); break;
}
},
itemBuilder: (context) => [
PopupMenuItem(
child: Text(AppLocalizations.of(context).settings),
value: 0
),
PopupMenuItem(
child: Text(AppLocalizations.of(context).logout),
value: 1
)
],
icon: Icon(
Icons.settings,
),
)
],
)
)
);
}
I had the same issue (except: I am using GetX for Navigation.
I think this happens because flutter is closing the PopupMenuButton automatically and because navigation happens to fast, it closes the new route instead of the menuButton.
I solved it like this in the navigating method:
void edit() async {
await Future.delayed(const Duration(milliseconds: 10));
Get.toNamed('/new/route'); // same like Navigator.of(context).pushNamed()
}
It's not beautiful... i know. But it works XD
I also faced this problem and solved it as follows:
First you must remove onTap or make its value null.
Second, give a value to the value parameter in PopupMenuItem. You can give any type like int or String or whatever you want since value is dynamic.
then, pass a function to the onSelected parameter in the PopupMenuButton.
Inside the onSelected function you can choose what you want to do when any of the menu items is pressed.
In your case, your code should be like this 👇
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
child: Text(...),
value: 'settings',
),
],
onSelected: (value){
if(value == 'settings'){
_openSettings(ctx);
}
},
),
you need to invoke the future function using ()=>, and pass Build Context for the function to know which context to navigate from.
so your button will change like this:
IconButton(onPressed:()=> _openSettings(context), icon: Icon(Icons.settings))
and your function should be like this:
Future<void> _openSettings(BuildContext ctx) async {
print('settings');
await Navigator.push(
ctx, MaterialPageRoute(builder: (context) => SettingPage()));
print('settings completed');
}
here is the entire code that I've tested the issue on:
class PlayGround extends StatefulWidget {
const PlayGround({Key? key}) : super(key: key);
#override
_PlayGroundState createState() => _PlayGroundState();
}
class _PlayGroundState extends State<PlayGround> {
final _title =
TextEditingController.fromValue(TextEditingValue(text: 'initialTitle'));
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MyApp'),
actions: [
PopupMenuButton<int>(
itemBuilder: (context) => [
PopupMenuItem(
child: Text(AppLocalizations.of(context).settings),
onTap: () {},
),
PopupMenuItem(
child: Text(AppLocalizations.of(context).logout),
onTap: () {},
)
],
icon: Icon(
Icons.settings,
),
),
IconButton(
onPressed: () => _openSettings(context),
icon: Icon(Icons.settings))
],
),
body: Container());
}
Future<void> _openSettings(BuildContext ctx) async {
print('settings');
await Navigator.push(
ctx, MaterialPageRoute(builder: (context) => SettingPage()));
print('settings completed');
}
}
class SettingPage extends StatelessWidget {
const SettingPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
}

How to change the state of an Action widget in an AlertDialog

I want to validate the user input and then enable the Dialog's Submit action.
Using StatefulBuilder inside the dialog's content doesn't work here, since the actions does not live in the content Widget. So how would one go about changing a property on an action widget?
Currently my workaround is to not use actions, and have a Row of buttons inside the dialog's content, which is OK, but I'd like to know whether there is a better way.
I feel that using a Provider just to hold the "validation result" is a bit overkill.
EDIT: Example demonstrating that a StatefulBuilder allows only inner widgets to be updted with state changes. The button sets enabled, but the of two "action buttons" only the one inside the StatefulBuilder is rebuilt. The AlertDialog actions does not get updated.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dialogs'),
),
body: Center(
child: TextButton(
child: Text('Clickme'),
onPressed: _dialogWithAction,
),
),
);
}
Future<void> _dialogWithAction() async {
showDialog<bool>(
context: context,
builder: (BuildContext dialogContext) {
bool enabled = false;
return AlertDialog(
title: Text('Add Address'),
content: StatefulBuilder(builder: (context, innerSetState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
child: Text('Enable Submit'),
onPressed: () {
enabled = true;
innerSetState(() {});
},
),
Row(
children: [
// This Workaround widget is rebuild by the stateSetter
TextButton(
onPressed: enabled ? () => Navigator.pop(context) : null,
child: Text('Submit'),
)
],
)
],
);
}),
actions: <Widget>[
// This Widget does not get rebuilt when "enabled" is changed
TextButton(
child: Text('Submit'),
onPressed: enabled
? () {
Navigator.of(dialogContext).pop();
}
: null,
),
],
);
},
);
}
}

Is there a way to configure willPopScope to catch a custom pop navigation in flutter?

Is there a way to configure willPopScope to catch a custom pop navigation as follows? I have a custom Raisedbutton widget with onPressed to navigate back to the previous page.
But willPopScope doesn't catch the navigation as it does for the AppBar back button. I'm not sure if this is even possible. Please advise. Thanks!
WillPopScope(
child: Scaffold(
body: Container(
child: Center(
child: RaisedButton(
onPressed:(){
return Navigator.pop(context);
},
child: Text("Go Back),
),
),
),
),
onWillPop: () async {
// code to show a modal
}
);
Here is a full example to achieve your goal. WillPopScope needs a maybePop call in order to do your logic:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: RaisedButton(
child: Text('Jump To Next Screen'),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => ModalScreen())),
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class ModalScreen extends StatefulWidget {
#override
_ModalScreenState createState() => _ModalScreenState();
}
class _ModalScreenState extends State<ModalScreen> {
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: AppBar(
leading: InkResponse(
child: Icon(Icons.arrow_back),
onTap: () => Navigator.of(context).maybePop(),
),
),
backgroundColor: Colors.blue,
body: Center(
child: RaisedButton(
child: Text('Let\'s go back'),
onPressed: () {
Navigator.of(context).maybePop();
},
),
),
),
onWillPop: () => _willPop(context),
);
}
Future<bool> _willPop(BuildContext context) {
final completer = Completer<bool>();
showModalBottomSheet(
context: context,
builder: (buildContext) {
return SizedBox(
height: 200,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Text('Are you sure?'),
),
MaterialButton(
child: Text('YES'),
onPressed: () {
completer.complete(true);
Navigator.of(context).pop();
}),
MaterialButton(
child: Text('NO'),
onPressed: () {
completer.complete(true);
}),
],
),
);
});
return completer.future;
}
}
Once you return true for your modal, you also need to Pop the screen as the modal has his own context needed to be popped.
Final result is the following: