MaterialPageRoute not opening new screen - flutter

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'),
),
);
}
}

Related

Flutter: Adding button shows up behind ListView, "duplicate child" error

I'm trying to add a button to navigate to another screen but I'm not sure how to get it on the bottom of my list instead of behind it. This is my current list:
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.teal[800],
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: ListView.builder(
itemCount: Type.samples.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BoardingDetail(boarding: Type.samples[index]);
},
),
);
},
child: buildBoardingCard(Type.samples[index]),
);
},
),
),
);
}
And I think this is the code I want to add to navigate to a new screen, I got this code from https://docs.flutter.dev/cookbook/navigation/navigation-basics
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
I tried to integrate the navigation button into my code but it says I have "duplicate child". What is the proper way to do this?
You have to nest the ListView and ElevatedButton in a SingleChildScrollView with a Column
You can try running this to see how it is implemented:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "ListView.builder",
theme: ThemeData(primarySwatch: Colors.green),
debugShowCheckedModeBanner: false,
home: const ListViewBuilder());
}
}
class ListViewBuilder extends StatelessWidget {
const ListViewBuilder({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("ListView.builder")),
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 8,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("List item $index"));
},
),
ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigate back to first route when tapped.
},
child: const Text('Go back!'),
),
),
);
}
}

How to close specific Dialog

I am opening a dialog from another dialog and trying to close the 1st dialog, but it is closing the recent dialog. Similar kind of git issue.
I've tried
putting ValueKey on AlertDialog
using rootNavigator:true while pop
keeping context into variable and doing Navigator.of(specifiqContext).pop()
But none of them is working.
Code to reproduce the issue on dartPad.
class MultiDialogTest extends StatefulWidget {
const MultiDialogTest({Key? key}) : super(key: key);
#override
State<MultiDialogTest> createState() => _MultiDialogTestState();
}
class _MultiDialogTestState extends State<MultiDialogTest> {
BuildContext? dialog1Context, dialog2Context;
Future<void> _showDialog1(BuildContext context) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (c) {
dialog1Context = c;
return AlertDialog(
key: const ValueKey("dialog 1"),
title: const Text("Dialog 1"),
content: ElevatedButton(
child: const Text("close dialog2"),
onPressed: () {
if (dialog2Context != null) {
Navigator.of(dialog2Context!,).pop();
}
},
),
actions: [
ElevatedButton(
child: const Text("close this"),
onPressed: () {
Navigator.of(c, rootNavigator: true).pop();
},
),
],
);
});
dialog1Context = null;
}
Future<void> _showDialog2(BuildContext context) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (c) {
dialog2Context = c;
return AlertDialog(
key: const ValueKey("dialog 2"),
title: const Text("Dialog 2"),
actions: [
ElevatedButton(
child: const Text("close this"),
onPressed: () {
Navigator.of(c, rootNavigator: true).pop();
},
),
],
content: Column(
children: [
ElevatedButton(
onPressed: () async {
await _showDialog1(context);
},
child: const Text("Open dialog 1"),
),
],
),
);
});
dialog2Context = null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
_showDialog2(context);
},
child: const Text("show dialog 2"),
),
),
);
}
}
How can I close bellow Dialog(Dialog 2) without closing above(Dialog 1).
I don't like to close both and reopen the Dialog 1.
You need to pass context of the dialog you want to close (parentContext) and call:
Navigator.pop(parentContext); // close parent
Navigator.pop(context); // close current
Create a separate context and pass the correct context which one you want to close to the Navigator.pop(yourContextThatYouWishToClose)
Navigator.pop(dialogContext);
Here is the example code.
BuildContext dialogContext; // <<----
showDialog(
context: context, // <<----
barrierDismissible: false,
builder: (BuildContext context) {
dialogContext = context;
return Dialog(
child: new Row(
mainAxisSize: MainAxisSize.min,
children: [
new CircularProgressIndicator(),
new Text("Loading"),
],
),
);
},
);
await _longOperation();
Navigator.pop(dialogContext);
What you could do is pop twice in showDialog1 and then await for showDialog1 immediately.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const MultiDialogTest(),
);
}
}
class MultiDialogTest extends StatefulWidget {
const MultiDialogTest({Key? key}) : super(key: key);
#override
State<MultiDialogTest> createState() => _MultiDialogTestState();
}
class _MultiDialogTestState extends State<MultiDialogTest> {
Future<void> _showDialog1(BuildContext context) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (c) {
return AlertDialog(
key: const ValueKey("dialog 1"),
title: const Text("Dialog 1"),
content: ElevatedButton(
child: const Text("close dialog2"),
onPressed: () async {
Navigator.of(context).pop();
Navigator.of(context).pop();
await _showDialog1(context);
},
),
actions: [
ElevatedButton(
child: const Text("close this"),
onPressed: () {
Navigator.of(c).pop();
},
),
],
);
});
}
Future<void> _showDialog2(BuildContext context) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (c) {
return AlertDialog(
key: const ValueKey("dialog 2"),
title: const Text("Dialog 2"),
actions: [
ElevatedButton(
child: const Text("close this"),
onPressed: () {
Navigator.of(c).pop();
},
),
],
content: Column(
children: [
ElevatedButton(
onPressed: () async {
await _showDialog1(context);
},
child: const Text("Open dialog 1"),
),
],
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
_showDialog2(context);
},
child: const Text("show dialog 2"),
),
),
);
}
}
When you use showDialog what happens under the hood is a simple push on the Navigator, this will add the Dialog on the top of the current Navigator as a new Route.
All the pop methods in Navigator simply pop from the topmost route so this is not easily feasible.
A dirty hack may be to pop twice and show again the first dialog like in this sample that works in your dartpad sample
onPressed: () {
if (dialog2Context != null) {
Navigator.of(dialog2Context!).pop();
Navigator.of(dialog2Context!).pop();
_showDialog1(context);
}
},
In my opinion, having a dialog spawning another dialog its not the best UX you can provide to your user, but you can always check which routes are involved by using the inspector:
https://docs.flutter.dev/development/tools/devtools/inspector
in this case, you can quickly check that the dialog will be always on top (in this case the latest of the tree), the proper way to fix this should be to create several navigators and decide which one to use for showing your dialog, but that will complexity a lot of your code!

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(),
);
}
}

Flutter update list with state managment

I am showing listview in my 2 pages. On the first page just simply showing the listView and on the other page I am adding and view the array by listView builder. Now the issue I am facing is when I add something in an array it's not changing in ListView builder so I can manage it by setState but when I go back on the first screen result isn't changing on that screen.
I have generated some example code to clear
var listArray = [];
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListWidget()),
);
},
child: Container(
color: Colors.blue,
height: 100,
width: 100,
child: Text('Go List'),
),
),
Expanded(
child: ListView.builder(
itemCount: listArray.length,
itemBuilder: (BuildContext context, int index) {
return Text('name ${listArray[index]['name']} id ${listArray[index]['id']}');
}),
),
],
),
);
}
}
class ListWidget extends StatefulWidget {
const ListWidget({Key? key}) : super(key: key);
#override
_ListWidgetState createState() => _ListWidgetState();
}
class _ListWidgetState extends State<ListWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.of(context).pop(),
),
),
body: Column(
children: [
GestureDetector(
onTap: (){
setState(() {
listArray.add({'name' : 'test', 'id' : 101, 'comments': 'xyzzz'});
});
},
child: Container(
color: Colors.blue,
height: 100,
width: 100,
child: Text('Add to list'),
),
),
Expanded(
child: ListView.builder(
itemCount: listArray.length,
itemBuilder: (BuildContext context, int index) {
return Text('name ${listArray[index]['name']} id ${listArray[index]['id']}');
}),
)
],
),
);
}
}
I am looking in documentation for app state managment but not able to find something for list update. If any help in code or some example where I can find answer similar to this will be great.
Try to update state of first widget when returning to it, so in _FirstPageState write this:
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListWidget()),
);
setState({});
}
Declaring a global variable like listArray is a bad practice. You should probably use some state management packages like provider or similar.

Flutter screen that contains a scrollable and clickable tiles at the left side of the screen in drawable

I'm trying to create a screen in flutter/dart that has three horizontal lines at the top left corner of the screen and when clicked on it, it displays a list of clickable texts, when clicked on it, it has to take to the next page. Any help will be appreciated.
ListTile and ListView widgets are used to represent a tile as a list in flutter and Drawer widget is the horizontal lines things that you are talking about, i suppose.
Here is the sample code for it:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget{
#override
_State createState() => new _State();
}
class _State extends State<HomePage> {
bool _value1 = false;
bool _value2 = false;
void _onChanged1(bool value) => setState(() => _value1 = value);
void _onChanged2(bool value) => setState(() => _value2 = value);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: Color(0xFF293b72),
title: new Text('Hughes InstaGrade'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
//new DrawerHeader(child: new Text(''),),
new ListTile(
title: new Text('Home'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
},
),
new ListTile(
title: new Text('New Dull Grade'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FirstForm(),
),
);
},
),
//new Divider(),
new ListTile(
title: new Text('Search'),
onTap: () {
},
),
//new Divider(),
new ListTile(
title: new Text('Filter'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FilterPage(),
),
);
},
),
new ListTile(
title: new Text('Forms'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Forms(title: "Forms"),
),
);
},
),
new SwitchListTile(
value: _value2,
onChanged: _onChanged2,
title: new Text('My Bits/All Bits', ),
),
new ListTile(
title: new Text('Log Out'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginPage(),
),
);
},
),
// new Switch(value: _value1, onChanged: _onChanged1),
],
)
),
body: DataScreen(),
);
}
}
String _formatDate(String date) {
DateTime d = DateTime.parse(date);
return DateFormat("dd-MM-yyyy").format(d);
}
}
class DataScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Data Screen"),
),
body: Text("Sample App"),),
);
}
}