Reputation: 709
Trying to use a set of nested PopupMenuButtons in a flutter app. The first menu opens as expected. The second menu opens only after tapping many times, closing the first menu, re-opening it, i.e. random behavior. Same is true for the third menu. Sometimes the first or second menu close prematurely without having collected all three pieces of information from the user. What is wrong in my code below???
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:localstore/localstore.dart';
import 'package:http/http.dart' as http;
late Map<String, dynamic> dirList;
List eventList = [];
List eventYearList = [];
List eventDayList = [];
String eventName = '';
String eventYear = '';
String eventDay = '';
String eventDomain = '';
late Map<String, String> eventInfo;
String eventTitle = "Selecteer een evenement";
// create a list of maptypes, just with the names of the maptypes in Dutch
const List ourMapTypes = ['Wegenkaart', 'Satelliet met labels',
'Satelliet zonder labels', 'Terrein', 'Open Sea Map'];
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late GoogleMapController mapController;
MapType currentMapType = MapType.normal;
final LatLng initialMapPosition = const LatLng(52.2, 4.535);
@override
void initState() {
super.initState();
}
Future<void> _onMapCreated(GoogleMapController controller) async {
mapController = controller;
// Get the list of events ready for selection
dirList = await fetchDirList();
dirList.forEach((k, v) => eventList.add(k));
eventYearList = [];
eventDayList = [];
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.green[900],
title: PopupMenuButton(
offset: const Offset(0,40),
child: Text(eventTitle),
itemBuilder: (BuildContext context) {
return eventList.map((events) {
return PopupMenuItem(
height: 30.0,
value: events,
child: PopupMenuButton(
offset: const Offset(30,0),
child: Text(events),
itemBuilder: (BuildContext context) {
return eventYearList.map((years) {
return PopupMenuItem(
height: 30.0,
value: years,
child: PopupMenuButton(
offset: const Offset(30,0),
child: Text(years),
itemBuilder: (BuildContext context) {
return eventDayList.map((days) {
return PopupMenuItem(
height: 30.0,
value: days,
child: Text(days)
);
}).toList();
},
onSelected: (eventDayList == []) ? null : newEventSelected,
),
);
}).toList();
},
onSelected: (eventYearList == []) ? null : selectEventDay,
),
);
}).toList();
},
onSelected: selectEventYear,
),
actions: <Widget>[
PopupMenuButton(
child: Image.asset('assets/images/mapicon.png'),
offset: Offset(0,55),
tooltip: 'Selecteer een kaarttype',
onSelected: selectMapType,
itemBuilder: (BuildContext context) {
return ourMapTypes.map((types) {
return PopupMenuItem(
height: 30.0,
value: types,
child: Text(types)
);
}).toList();
},
),
],
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: initialMapPosition,
zoom: 12.0,
),
mapType: currentMapType,
),
bottomNavigationBar: Text('bottombar'),
),
);
}
// Routine to change the Type of the map based on the user selection
void selectMapType(selectedMapType) {
setState(() { // Causes the app to rebuild with the selected choice.
switch (selectedMapType) {
case "Wegenkaart":
currentMapType = MapType.normal;
break;
case "Satelliet met labels":
currentMapType = MapType.hybrid;
break;
case "Satelliet zonder labels":
currentMapType = MapType.satellite;
break;
case "Terrein":
currentMapType = MapType.terrain;
break;
case "Open Sea Map":
currentMapType = MapType.normal;
break;
default:
currentMapType = MapType.normal;
break;
}
});
}
void selectEventYear(event) {
setState(() {
eventName = event;
eventYearList = [];
dirList[event].forEach((k, v) => eventYearList.add(k));
eventYearList = eventYearList.reversed.toList();
eventDayList = [];
});
}
void selectEventDay(year) {
setState(() {
eventYear = year;
eventDayList = [];
eventTitle = eventName + '/' + year;
if (dirList[eventName][eventYear].length != 0) {
dirList[eventName][eventYear].forEach((k, v) => eventDayList.add(k));
} else {
newEventSelected('');
}
});
}
void newEventSelected(day) {
setState(() {
eventDay = day;
eventDomain = eventName + '/' + eventYear;
if (eventDay != '') eventDomain = eventDomain + '/' + eventDay;
eventTitle = eventDomain; // for the time being
eventYearList = [];
eventDayList = [];
});
}
Future<Map<String, dynamic>> fetchDirList() async {
final response = await http
.get(Uri.parse('https://tt.zeilvaartwarmond.nl/get-dirlist.php?tst=true&msg=simple'));
if (response.statusCode == 200) {
return (jsonDecode(response.body));
} else {
throw Exception('Failed to load dirList');
}
}
}
Upvotes: 0
Views: 671
Reputation: 63749
The default behavior of PopupMenuButton
is to close it after selecting. While using nested PopupMenuButton
you need to be careful about context
, which one when and how it is closing.
Next issue comes from the padding
of PopupMenuItem
, each item does not take full size.
You can use PopupMenuItem
's onTap
or onSelected
from PopupMenuButton
to find selected value. If you want to update UI on dialog, check StatefulBuilder
.
This is a test snippet:
PopupMenuButton(
child: const Text("POP U"),
onSelected: (value) {
print(value);
},
itemBuilder: (BuildContext context_p0) {
return [
const PopupMenuItem(value: "item: p1", child: Text("Item:p1 ")),
PopupMenuItem(
value: "item: p1",
onTap: () {},
padding: EdgeInsets.zero,
child: PopupMenuButton(
padding: EdgeInsets.zero,
child: Container(
alignment: Alignment.center,
height: 48.0, //default height
width: double.infinity,
child: Text("inner PopUp Menu"),
),
itemBuilder: (context_p1) {
return [
PopupMenuItem(
value: "inner p2",
child: Text("inner p2: close with parent "),
onTap: () {
Navigator.of(context_p1).pop();
},
),
const PopupMenuItem(
value: 'inner p1',
child: Text("inner p1, just close this one"),
),
];
},
),
)
];
},
),
Upvotes: 2