Crazy Lazy Cat's answer is great and compact. But if you need further operations and can't use a map like that, then you'd have to go for a solution similar to your code. Though these methods has a caveat that lists/iterables shouldn't be modified from WITHIN the futures that are executed concurrently/asynchronously. Go to the end of my answer to understand this more.
Anyways as per the solution, in this case you have two options:
Option A.
Iterating over the list in a batch manner using Future.wait
In this case, all iterations would be executed in parallel (theoretically). This ensures utmost performance/concurrency.
It boils down to storing the futures before asynchronously executing them using Future.wait
.
class Song {
int id;
}
class Playlist {
List<int> songIds;
}
Future<Playlist> getPlaylist(int id) async {
return Playlist();
}
Future<Song> getSong(int songId) async {
return Song();
}
/// Returns list of songs from a `Playlist` using [playlistId]
Future<List<Song>> getSongsFromPlaylist(int playlistId) async {
/// Local function that populates [songList] at [songListIndex] with `Song` object fetched using [songId]
Future<void> __populateSongList(
List<Song> songList,
int songListIndex,
int songId,
) async {
// get the song by its id
Song song = await getSong(songId);
print(song.id);
// add the song to the pre-filled list of songs at the specified index to avoid `ConcurrentModificationError`
songList[songListIndex] = song;
print(
'populating list at index $songListIndex, list state so far: $songList');
} // local function ends here
// get the playlist object by its id
final playlist = await getPlaylist(playlistId);
// create a filled list of pre-defined size to avoid [ConcurrentModificationError](https://api.flutter.dev/flutter/dart-core/ConcurrentModificationError-class.html)
List<Song> songList = List<Song>.filled(playlist.songIds.length, null);
// store futures and execute them in a batch manner using [Future.wait](https://api.dart.dev/stable/2.7.1/dart-async/Future/wait.html)
List<Future<void>> songFutures = [];
for (int listIndex = 0; listIndex < playlist.songIds.length; listIndex++) {
final songId = playlist.songIds[listIndex];
songFutures.add(__populateSongList(songList, listIndex, songId));
}
// execute multiple futures concurrently/in parallel
List<void> songFuturesResult = await Future.wait(songFutures);
/* ALSO VALID
List<void> _ = await Future.wait(songFutures);
await Future.wait(songFutures);
*/
print('returned list: $songList');
return songList;
}
Option B.
Iterating over the list sequentially
In this case, each iteration is awaited until the next is executed. Performance is not as good as option A since each iteration/invocation is awaited halting control flow, the following iteration/invocation won't start until the previous one finishes. This method is somehow safer than the previous one in regard to ConcurrentModificationError
For references only this block is different from the previous option
// populate songList sequentially -- each iteration/song halted until the previous one finishes execution
for (int listIndex = 0; listIndex < playlist.songIds.length; listIndex++) {
final songId = playlist.songIds[listIndex];
await __populateSongList(songList, listIndex, songId);
}
But here is the full solution anyways:
class Song {
int id;
}
class Playlist {
List<int> songIds;
}
Future<Playlist> getPlaylist(int id) async {
return Playlist();
}
Future<Song> getSong(int songId) async {
return Song();
}
/// Returns list of songs from a `Playlist` using [playlistId]
Future<List<Song>> getSongsFromPlaylist(int playlistId) async {
/// Local function that populates [songList] at [songListIndex] with `Song` object fetched using [songId]
Future<void> __populateSongList(
List<Song> songList,
int songListIndex,
int songId,
) async {
// get the song by its id
Song song = await getSong(songId);
print(song.id);
// add the song to the pre-filled list of songs at the specified index to avoid `ConcurrentModificationError`
songList[songListIndex] = song;
print(
'populating list at index $songListIndex, list state so far: $songList');
} // local function ends here
// get the playlist object by its id
final playlist = await getPlaylist(playlistId);
// create a filled list of pre-defined size to avoid [ConcurrentModificationError](https://api.flutter.dev/flutter/dart-core/ConcurrentModificationError-class.html)
List<Song> songList = List<Song>.filled(playlist.songIds.length, null);
// populate songList sequentially -- each iteration/song halted until the previous one finishes execution
for (int listIndex = 0; listIndex < playlist.songIds.length; listIndex++) {
final songId = playlist.songIds[listIndex];
await __populateSongList(songList, listIndex, songId);
}
print('returned list: $songList');
return songList;
}
Explanation
Addition/Removal to the accessed collection (array, map, ...) must be done outside the futures, otherwise a runtime error will be thrown for concurrently modifying an iterable during iteration.
References
Solutions and discussions