My question is to help me figure out the logic of piecing these parts together.

I have

1 - An API returning JSON which contains a List<>? with data in it replicated ~5 times at the endpoint headsign

2 - A Metadata pop up which is populated by a _showDialog function with data from above API

3 - A Listbody function (within _showDialog) to show the data in Text

My API is called (Which I think will need to be changed to .toList function)

Future<di.Departures?> getDepartures(id) async { 
   var client = http.Client();
   di.Departures? departures; 

    try{
    var response = await client.get(Uri.parse('https_call'));
    if (response.statusCode == 200) {
   var jsonString = response.body;
   var jsonMap = json.decode(jsonString); 

    departures = di.Departures.fromJson(jsonMap);

  }
} catch(e) {

  print("Exception Happened: ${e.toString()}");
}

}

How the data is returned

"boards": [
        {
            "place": {
                    x
                    x
                },
                 x
            },
            "departures": [
                {
                    "time": "2022-02-21T10:53:00Z",
                    "transport": {

                        "name": "District",
                        "color": "#007A33",
                        "textColor": "#FFFFFF",
               >>>      "headsign": "Ealing Broadway Station",
                        "shortName": "District",
                        "longName": "Ealing 
                    },
                    "agency": {
                        x
                    }
                },

The API data is added to the individual mapMarker and passed through to the metadata pop up _showDialog via _hereMapController

...
 getDepartures(id).then((departures) async {

    var boards = await getDepartures(id);
    for (di.Board boards in boards!.boards!) { 

    var title = boards.place!.name.toString();

    var headsign = boards.departures!.first.transport!.headsign;

    var title = "$title";

    Metadata metadata = new Metadata();
    metadata.setString("key_poi", headsign);
    metadata.setString("title", title.toString());
    mapMarker.metadata = metadata;

    _hereMapController.mapScene.addMapMarker(mapMarker);
    _mapMarkerList.add(mapMarker);
...

hereMapController then passes it to _showDialog

      MapMarker topmostMapMarker = mapMarkerList.first;
      Metadata? metadata = topmostMapMarker.metadata;
      if (metadata != null) {
        String headsign = metadata.getString("key_poi") ?? "No message found.";
        String title = metadata.getString("title") ?? "No";

        _showDialog(title, headsign);
        return;
      }

      _showDialog("x", "No metadata attached.");
    });
  }

My Listbody in _showDialog

Future<void> _showDialog(String title, String headsign) async {
    return showDialog<void>(
      context: context,
      barrierDismissible: false,
builder: (BuildContext context) {
        return AlertDialog(
          backgroundColor: Color(0xff2AC6FF),
          shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10)),
          title: Text(title),
          content: SingleChildScrollView(
            scrollDirection: Axis.vertical,
            child: 
          ListBody(
              children: <Widget>[
                Text(headsign,
                style: TextStyle(
              fontSize: 18,
              color: Color(0xff2F2F2F),
              fontWeight: FontWeight.w400,
              ),
              ),
              ],
            ),
...

As of now, my pop-up shows the text value of the first example returned by the API.

I want to take the values returned (up to 5 max for example) and display them in a DropDown list on the pop-up.

I presume I will need to form a DropDown somewhere along?

The problem I am having is needing to use the constructor first when selecting the data I cannot see another way to take all examples returned (up to 5 max) of the specific field headsign.

Thank you

Update

I need to pull the data and add it as key/value here to function with the rest of my codebase as this is how the rest functions.

...

    Metadata metadata = new Metadata();
    metadata.setString("key_poi", headsign);
    metadata.setString("title", title.toString());
    mapMarker.metadata = metadata;

    _hereMapController.mapScene.addMapMarker(mapMarker);
    _mapMarkerList.add(mapMarker);

    mapMarkerList.where((m) => m.metadata.getString("key_poi") != null && m.metadata.getString("title")).take(5).toList();
    var values = mappedList.map((m) => { 'headsign': m.getString('key_poi'), 'title': m.getString('title') });

...

I have the API called by this point and the data is ready.

When I try to add code as seen above I get errors because mapMarkerList & mappedList aren't defined there. Should I change mapMarkerList as it is already defined as the List of physical map markers and try to define it? Thank you for your help


Solution 1: Roman Jaquez

As mentioned in the comments, you should take the filtering of the mapMarkerList outside of the loop that populates it, as in:


getDepartures(id).then((departures) async {

    var boards = await getDepartures(id);

    // loop to populate the mapMarkerList
    for (di.Board boards in boards!.boards!) { 
       var title = boards.place!.name.toString();

       var headsign = boards.departures!.first.transport!.headsign;

       var title = "$title";

       Metadata metadata = new Metadata();
       metadata.setString("key_poi", headsign);
       metadata.setString("title", title.toString());
       mapMarker.metadata = metadata;

       _hereMapController.mapScene.addMapMarker(mapMarker);
       _mapMarkerList.add(mapMarker);
   }

   // now, outside of the loop, do the filtering

   // take the top 5 results in which "key_poi" and "title" is not null:
   var mappedList = mapMarkerList.where((m) => m.metadata.getString("key_poi") != null && 
   m.metadata.getString("title")).take(5).toList();

   // now, from the list above, map each result into a key-value pair
   // dictionaries that only contains "headsign" and "title"
    var mappedValues = mappedList.map((m) => { 'headsign': m.getString('key_poi'), 'title': m.getString('title') });

   // this list will look like:
   // [{ headsign: 'value1', title: 'title1'}, { headsign: 'value2', title: 'value2' }]

   // Now is when you feed the mappedValues list to your showDialog
   // where you can show them all in a dropdown, but you'll have to change the signature
  showDialog(mappedValues, 'Results');

}

Then, your (updated) showDialog should look something like this:


Future<void> _showDialog(List<Map<String, dynamic>> values, String title) async {
    return showDialog<void>(
      context: context,
      barrierDismissible: false,
builder: (BuildContext context) {
        return AlertDialog(
          backgroundColor: Color(0xff2AC6FF),
          shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10)),
          title: Text(title),
          content: SingleChildScrollView(
            scrollDirection: Axis.vertical,
            child: 
          ListBody(
              children: List.generate(
                values.length, (index) {
                   return Text(values[index]['headsign']);
                }
              ),
            ),
...

Now you can replace the ListBody by a Dropdown, since inside of the dialog now you can use the values list, which contains the values you need.