Reputation: 1619
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,
),
)
],
)
)
);
}
Upvotes: 6
Views: 4977
Reputation: 1
This worked for me. When the menu item is tapped, it schedules a callback to be executed after the current frame is rendered. This ensures that the navigation action (using Get.to(() => Settings());) is performed after the widget tree is fully built, which is necessary for navigation to work correctly.
SchedularBinding.instance!.addPostFrameCallback((_){
//your code here,
//this is executed after the first frame is rendered
});
PopupMenuItem(
onTap: (){
SchedulerBinding.instance!.addPostFrameCallback((_) {
Get.to(() => Settings());
});
hope it helps:)
Upvotes: 0
Reputation: 359
I also faced this problem and solved it as follows:
onTap
or make its value null
.value
parameter in PopupMenuItem
. You can give any type like int
or String
or whatever you want since value is dynamic.onSelected
parameter in the PopupMenuButton
.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);
}
},
),
Upvotes: 6
Reputation: 21
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
Upvotes: 2
Reputation: 1270
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');
}
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(),
);
}
}
Upvotes: 1