HWK
HWK

Reputation: 709

Trying to use a nested set of PopupMenuButtons in a flutter app

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

Answers (1)

Md. Yeasin Sheikh
Md. Yeasin Sheikh

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

Related Questions