11

This forEach works just fine

var newMarkers = new List<Marker>();
providers.forEach((p) {
  var marker = markerFrom(p);
  newMarkers.add(marker);
  print("got one");
});

_markers = newMarkers;

but this map doesn't ever get called when placed in the exact same place as the forEach:

_markers = providers.map((p) => markerFrom(p));

Additionally, this is the markerFrom method:

  Marker markerFrom(FoodProvider provider) {
  var marker = new Marker(new MarkerOptions()
    ..map = _map
    ..position = new LatLng(provider.latitude, provider.longitude)
    ..title = provider.name
    ..icon = 'http://maps.google.com/mapfiles/ms/icons/red-dot.png'
  );

  var infoWindow = new InfoWindow(new InfoWindowOptions()..content = marker.title);

  marker.onClick.listen((e) {
    infoWindow.open(_map, marker);
  });

  return marker;
}
Erik
  • 1,246
  • 13
  • 31
  • do you have any error message ? an careful, if you want to manipulate a `List`, `map` will produce an `Iterable`, so you have to do `_markers = providers.map((p) => markerFrom(p)).toList();`, but it depends of what you want to do with `_markers` – Hadrien Lejard Jun 01 '17 at 07:50

1 Answers1

22

The map function on Iterable is lazy. It just creates a wrapper around the original Iterable, but it doesn't actually do anything until you start iterating.

That's a good thing, even if it can be surprising if you are used to an eager map function from other languages. It means that you can combine operations on iterables like:

listOfStrings
    .map((x) => complicatedOperationOnString(x))
    .take(2)
    .forEach(print);

This only does the complicated operation on the first two strings.

If you want a list of the markers, you need to call toList after the map:

_markers = providers.map(markerFrom).toList();

In general, you should be very careful when the function passed to map has side-effects that you want to only happen once. If you iterate the result of map more than once, that effect will happen each time, so if you did:

_markers = providers.map(markerFrom);
int count = _markers.length;
_markers.forEach((marker) { doSomething(marker); });

you would risk the side effects happening twice, once for length and once for forEach, which both iterate _markers. It doesn't always happen (some iterables know their length without actually iterating), but it is always a risk. In those cases, either use forEach if the side-effect is the only thing you are after, or do an immediate toList to force all the operations, and then only look at the resulting list after that.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
lrn
  • 64,680
  • 7
  • 105
  • 121
  • Is there any way to just do a normal map? This behaviour is very inconsistent and confusing. – Cobolt Dec 02 '19 at 08:11
  • The `map` operation in Dart is always lazy, as is `where` and all other members of `Iterable`. As such, it's internally consistent. If you want to eagerly create a new list, all you have to do is follow the `map` operation with `.toList()`: `_markers = providers.map(markerFrom).toList();` or `_markers = [...providers.map(markerFrom)]`. If you want an alternative, I'd use is the list literal iteration: `[for (var p in providers) markerFrom(p)]`. It's not as short, but perhaps more readable. – lrn Dec 02 '19 at 15:19
  • This doesn't work with async functions in the map callback. I think perhaps it would need to be used in combination with Future.wait or similar. – Cobolt Dec 05 '19 at 17:13