rinukkusu / spotify-dart

A dart library for interfacing with the Spotify API.
BSD 3-Clause "New" or "Revised" License
206 stars 92 forks source link

FormatException #188

Closed addreeh closed 9 months ago

addreeh commented 10 months ago

Hi guys, any idea about this issue:

FormatException: Unexpected character (at character 1) Too many requests

I dont know if is an API timeout, if it is which will be the better solution?

rinukkusu commented 10 months ago

Seems like you hit a quota and got rate limited. But maybe we need to think about some library level handling of such a situation.

What it means for you right now is, that you need to wait until you can make a request again. In the Developer Dashboard you should be able to see your current volume.

addreeh commented 10 months ago

image image

Its probably that i hitted a quota because I tried a lot of things yesterday, but my question is if I deploy an app how can I handle these type of problems? Maybe creating another app and changing between them?

hayribakici commented 9 months ago

@addreeh You could show the user a message, that the app is currently not usable and that he/she should try it again later. I suggest to check the code for unnecessary requests (sometimes they are done in some for-loops) and look for alternatives (e.g. instead of requesting all albums for each artist, you could collect each albumId for each artist and do a getSeveralAlbums request). That way you would reduce requests.

addreeh commented 9 months ago

Mmmm yes, is so probably that i am doing request that are unnecessary, I would glad if you could check my code and tell me if I can resume or delete something, if you don't have time close the issue, I understand that this is not the place for that. Thanks for the support.

Pasted Code ```dart _initApp() async { try { playlistSongs.clear(); playlistArtists = []; playlistArtistsFull = []; playlistObject = await _getPlaylist(widget.playlistUrl); playlistDominantColor = await _getPlaylistDominantColor(); _checkDominantColor(); playlistDominantColorLight = _getLighterPlaylistDominantColor(); playlistSongs = await _getPlaylistSongs(); playlistDuration = _getPlaylistDuration(); playlistArtists = await _getArtists(); playlistArtistsFull = await _getFullArtist(); playlistStatistics = _getArtistStatistics(); setState(() { isLoading = false; }); } catch (e) { print("Error al obtener canciones: $e"); setState(() { isLoading = false; }); } } String _extractPlaylistId(String spotifyLink) { RegExp regExp = RegExp(r'/playlist/([a-zA-Z0-9]+)'); Match? match = regExp.firstMatch(spotifyLink); if (match != null && match.groupCount >= 1) { return match.group(1)!; } else { return ""; } } Future _getPlaylist(String url) async { var playlist = await spotifyApi.playlists.get(_extractPlaylistId(url)); return playlist; } void _checkDominantColor() { // Comparar los componentes de playlistDominantColor (rojo, verde y azul) con un umbral int umbral = 150; if (playlistDominantColor.red > umbral && playlistDominantColor.green > umbral && playlistDominantColor.blue > umbral) { bannerTextColor = Colors.black; } else { bannerTextColor = Colors.white; } } Future _getPlaylistDominantColor() async { try { var paletteGenerator = await PaletteGenerator.fromImageProvider( Image.network(playlistObject.images!.first.url!).image, ); return paletteGenerator.dominantColor!.color; } catch (e) { return const Color(0xFF000000); } } _getLighterPlaylistDominantColor() { try { final hslColor = HSLColor.fromColor(playlistDominantColor); playlistDominantColorLight = hslColor .withLightness((hslColor.lightness).clamp(0.20, 0.51)) .toColor(); return playlistDominantColorLight; } catch (e) { return const Color(0xFF000000); } } Future> _getPlaylistSongs() async { List songs = []; int offset = 0; int limit = 100; int cont = 1; while (true) { try { var tracks = await spotifyApi.playlists .getTracksByPlaylistId(playlistObject.id) .getPage(limit, offset); if (tracks.items != null && tracks.items!.isNotEmpty) { try { for (var track in tracks.items!) { songs.add(track); cont++; } offset += limit; } catch (e) { cont++; offset = cont; } } else { break; } } catch (e) { print(e); showTopSnackBar( Overlay.of(context), const CustomSnackBar.error( message: "Something went wrong. Try it again later.", ), ); } } return songs; } String _getPlaylistDuration() { num duration = 0; var uniqueArtists = {}; for (var song in playlistSongs) { duration += song.durationMs; for (var artist in song.artists) { if (artist.id != null && artist.name != null) { uniqueArtists.add(artist.id); } } } int seconds = (duration / 1000).round(); int hours = seconds ~/ 3600; int minutes = (seconds % 3600) ~/ 60; String formattedDuration = hours > 0 ? '$hours h ${minutes.toString().padLeft(2, '0')} min' : '$minutes min'; return formattedDuration; } _getArtists() async { var artistIds = {}; // Conjunto para almacenar IDs únicos de artistas var uniqueArtists = []; // Lista para almacenar artistas únicos for (var song in playlistSongs) { for (var artist in song.artists) { if (artist.id != null && artist.name != null) { if (!artistIds.contains(artist.id)) { // Si el ID del artista no está en el conjunto, agrega el artista artistIds.add(artist.id); uniqueArtists.add(artist); } } } } return uniqueArtists; } _getFullArtist() async { List artists = []; for (var artist in playlistArtists) { try { var newArtist = await spotifyApi.artists.get(artist.id); artists.add(newArtist); } catch (e) { print(e); showTopSnackBar( Overlay.of(context), const CustomSnackBar.error( message: "Something went wrong. Try it again later.", ), ); } } return artists; } List _getArtistStatistics() { List artistStatistics = []; for (var artist in playlistArtistsFull) { var artistTracks = []; for (var song in playlistSongs) { for (var songArtist in song.artists) { if (songArtist.id == artist.id) { artistTracks.add(song); break; } } } double percentage = double.parse( (artistTracks.length / playlistSongs.length * 100) .toStringAsFixed(2)); ArtistResult result = ArtistResult( artist, artistTracks, artistTracks.length, percentage, // color, ); artistStatistics.add(result); } artistStatistics.sort((a, b) => sortByPercentageAscending ? a.percentage.compareTo(b.percentage) : b.percentage.compareTo(a.percentage)); return artistStatistics; } ```
hayribakici commented 9 months ago
while (true) {
      try {
        var tracks = await spotifyApi.playlists
            .getTracksByPlaylistId(playlistObject.id)
            .getPage(limit, offset);
...
}
  1. So one thing, that looks suspicious is that you fetch all playlist items. If - let's say - a playlist has 300 tracks, but only - let's say - 15 are displayed, then it is indeed a waste of requests. As far as I understand your code, you want to display tracks from a playlist in a list (?). You could reduce requests by only GETting one Page at a time. This could be done with ListViews that allow to fetch more data once the end of the list is reached. Unfortunately I forgot the proper name of those lists, but with a quich search you would be able to find it.

_getFullArtist() async {
    List<spotify.Artist> artists = [];
    for (var artist in playlistArtists) {
      try {
        var newArtist = await spotifyApi.artists.get(artist.id);
        artists.add(newArtist);
      } catch (e) { ... }
    return artists;
  }
  1. You could use spotify.artists.list() here instead of requesting each artist individually. So in your case it would look like this:
_getFullArtist() async {
    // converting list of artists into list of artist id's
    var artistIds = playlistArtists.map((artist) => artist.id);
    // bulk retrieve artist information saving you the for-loop
    return await spotifyApi.artists.list(artistIds);
  }

Hope it helps :)

hayribakici commented 9 months ago

Seems like you hit a quota and got rate limited. But maybe we need to think about some library level handling of such a situation.

@rinukkusu Maybe tracking the number of requests?

addreeh commented 9 months ago

My purpose is to do an app to track a playlist, it shows the percentage of songs that each artist has, because of that I have to get all the tracks and then I have to get each artist of each song, because of that i have to use a lot of loops that makes the app slow, but Im sure that if I would know all the methods of the library it will be faster jajaja

Pasted Code ```dart // ignore_for_file: library_private_types_in_public_api, use_build_context_synchronously import 'package:flutter/material.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart'; import 'package:spotify/spotify.dart' as spotify; import 'package:top_snackbar_flutter/custom_snack_bar.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:transformable_list_view/transformable_list_view.dart'; import 'package:expansion_tile_card/expansion_tile_card.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:palette_generator/palette_generator.dart'; final credentials = spotify.SpotifyApiCredentials( "", ""); var spotifyApi = spotify.SpotifyApi(credentials); class ArtistResult { final spotify.Artist artist; final List tracks; final int total; final double percentage; // final Color color; ArtistResult(this.artist, this.tracks, this.total, this.percentage); } class Trackerfy extends StatefulWidget { final String playlistUrl; const Trackerfy(this.playlistUrl, {super.key}); @override _TrackerfyState createState() => _TrackerfyState(); } class _TrackerfyState extends State { bool showLoadingAnimation = true; var playlistObject; var playlistSongs = []; var playlistDuration = ""; var playlistArtists = []; var playlistArtistsFull = []; bool isLoading = true; bool sortByPercentageAscending = false; final TextEditingController _searchController = TextEditingController(); final bool _isSearching = false; var _filteredArtists = []; var playlistStatistics = []; Color playlistDominantColor = const Color(0xFF000000); Color playlistDominantColorLight = const Color(0xFF000000); Color bannerTextColor = Colors.white; @override void initState() { super.initState(); _initApp(); // Después de 5 segundos, oculta la animación de carga Future.delayed(const Duration(seconds: 5), () { setState(() { showLoadingAnimation = false; }); }); } _initApp() async { try { playlistSongs.clear(); playlistArtists = []; playlistArtistsFull = []; playlistObject = await _getPlaylist(widget.playlistUrl); playlistDominantColor = await _getPlaylistDominantColor(); _checkDominantColor(); playlistDominantColorLight = _getLighterPlaylistDominantColor(); playlistSongs = await _getPlaylistSongs(); playlistDuration = _getPlaylistDuration(); playlistArtists = await _getArtists(); playlistArtistsFull = await _getFullArtist(); playlistStatistics = _getArtistStatistics(); setState(() { isLoading = false; }); } catch (e) { print("Error al obtener canciones: $e"); setState(() { isLoading = false; }); } } String _extractPlaylistId(String spotifyLink) { RegExp regExp = RegExp(r'/playlist/([a-zA-Z0-9]+)'); Match? match = regExp.firstMatch(spotifyLink); if (match != null && match.groupCount >= 1) { return match.group(1)!; } else { return ""; } } Future _getPlaylist(String url) async { var playlist = await spotifyApi.playlists.get(_extractPlaylistId(url)); return playlist; } void _checkDominantColor() { // Comparar los componentes de playlistDominantColor (rojo, verde y azul) con un umbral int umbral = 150; if (playlistDominantColor.red > umbral && playlistDominantColor.green > umbral && playlistDominantColor.blue > umbral) { bannerTextColor = Colors.black; } else { bannerTextColor = Colors.white; } } Future _getPlaylistDominantColor() async { try { var paletteGenerator = await PaletteGenerator.fromImageProvider( Image.network(playlistObject.images!.first.url!).image, ); return paletteGenerator.dominantColor!.color; } catch (e) { return const Color(0xFF000000); } } _getLighterPlaylistDominantColor() { try { final hslColor = HSLColor.fromColor(playlistDominantColor); playlistDominantColorLight = hslColor .withLightness((hslColor.lightness).clamp(0.20, 0.51)) .toColor(); return playlistDominantColorLight; } catch (e) { return const Color(0xFF000000); } } Future> _getPlaylistSongs() async { List songs = []; int offset = 0; int limit = 100; int cont = 1; while (true) { try { var tracks = await spotifyApi.playlists .getTracksByPlaylistId(playlistObject.id) .getPage(limit, offset); if (tracks.items != null && tracks.items!.isNotEmpty) { try { for (var track in tracks.items!) { songs.add(track); cont++; } offset += limit; } catch (e) { cont++; offset = cont; } } else { break; } } catch (e) { print(e); showTopSnackBar( Overlay.of(context), const CustomSnackBar.error( message: "Something went wrong. Try it again later.", ), ); } } return songs; } String _getPlaylistDuration() { num duration = 0; var uniqueArtists = {}; for (var song in playlistSongs) { duration += song.durationMs; for (var artist in song.artists) { if (artist.id != null && artist.name != null) { uniqueArtists.add(artist.id); } } } int seconds = (duration / 1000).round(); int hours = seconds ~/ 3600; int minutes = (seconds % 3600) ~/ 60; String formattedDuration = hours > 0 ? '$hours h ${minutes.toString().padLeft(2, '0')} min' : '$minutes min'; return formattedDuration; } _getArtists() async { var artistIds = {}; // Conjunto para almacenar IDs únicos de artistas var uniqueArtists = []; // Lista para almacenar artistas únicos for (var song in playlistSongs) { for (var artist in song.artists) { if (artist.id != null && artist.name != null) { if (!artistIds.contains(artist.id)) { // Si el ID del artista no está en el conjunto, agrega el artista artistIds.add(artist.id); uniqueArtists.add(artist); } } } } return uniqueArtists; } _getFullArtist() async { List artists = []; for (var artist in playlistArtists) { try { var newArtist = await spotifyApi.artists.get(artist.id); artists.add(newArtist); } catch (e) { print(e); showTopSnackBar( Overlay.of(context), const CustomSnackBar.error( message: "Something went wrong. Try it again later.", ), ); } } return artists; } List _getArtistStatistics() { List artistStatistics = []; for (var artist in playlistArtistsFull) { var artistTracks = []; for (var song in playlistSongs) { for (var songArtist in song.artists) { if (songArtist.id == artist.id) { artistTracks.add(song); break; } } } double percentage = double.parse( (artistTracks.length / playlistSongs.length * 100) .toStringAsFixed(2)); ArtistResult result = ArtistResult( artist, artistTracks, artistTracks.length, percentage, // color, ); artistStatistics.add(result); } artistStatistics.sort((a, b) => sortByPercentageAscending ? a.percentage.compareTo(b.percentage) : b.percentage.compareTo(a.percentage)); return artistStatistics; } Matrix4 getTransformMatrix(TransformableListItem item) { const endScaleBound = 0.3; final animationProgress = item.visibleExtent / item.size.height; final paintTransform = Matrix4.identity(); if (item.position != TransformableListItemPosition.middle) { final scale = endScaleBound + ((1 - endScaleBound) * animationProgress); paintTransform ..translate(item.size.width / 2) ..scale(scale) ..translate(-item.size.width / 2); } return paintTransform; } _launchURL(url) async { final Uri uri = Uri.parse(url); if (!await launchUrl(uri)) { throw Exception('Could not launch $uri'); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF121212), body: ResponsiveBuilder( builder: (context, sizingInformation) { return Center( child: SizedBox( width: sizingInformation.isMobile ? MediaQuery.of(context).size.width : 450.0, child: showLoadingAnimation ? Center( child: LoadingAnimationWidget.horizontalRotatingDots( color: Colors.white, size: 50), ) : NestedScrollView( headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverAppBar( shadowColor: playlistDominantColorLight, automaticallyImplyLeading: false, backgroundColor: playlistDominantColor, title: Padding( padding: const EdgeInsets.only(left: 5), child: Text( playlistObject.name ?? "Playlist", style: TextStyle( fontFamily: "Circular Sp", fontSize: 18.0, fontWeight: FontWeight.bold, color: bannerTextColor, ), ), ), expandedHeight: 253, floating: true, pinned: true, flexibleSpace: FlexibleSpaceBar( background: Center( child: Column( children: [ SizedBox( height: sizingInformation.isMobile ? 85 : 65, ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(width: 20), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), boxShadow: [ BoxShadow( color: playlistDominantColorLight, spreadRadius: 2, blurRadius: 5, ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(10.0), child: Image.network( playlistObject.images!.first.url!, width: 120, height: 120, fit: BoxFit.cover, ), ), ), const SizedBox(width: 20), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( playlistObject.description!, style: TextStyle( fontFamily: "Circular Sp", fontSize: 12.0, fontWeight: FontWeight.normal, color: bannerTextColor, ), textAlign: TextAlign.center, ), const SizedBox(height: 10), Text( "Owner ➜${playlistObject.owner!.displayName!}", style: TextStyle( fontFamily: "Circular Sp", fontSize: 15.0, fontWeight: FontWeight.normal, color: bannerTextColor, ), textAlign: TextAlign.center, ), const SizedBox(height: 10), Text( playlistStatistics.isEmpty && playlistDuration == "" ? "${playlistObject.tracks!.total} songs" : playlistStatistics .isEmpty && playlistDuration != "" ? "${playlistObject.tracks!.total} songs ┃ $playlistDuration" : "${playlistObject.tracks!.total} songs ┃ $playlistDuration ┃ ${playlistStatistics.length} artists", style: TextStyle( fontFamily: "Circular Sp", fontSize: 12.0, fontWeight: FontWeight.normal, color: bannerTextColor, ), textAlign: TextAlign.center, ), ], ), ), const SizedBox(width: 20), ], ), Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.fromLTRB( 8, 10, 8, 0), child: TextField( cursorColor: bannerTextColor, controller: _searchController, style: TextStyle( fontFamily: "Circular Sp", color: bannerTextColor, ), decoration: InputDecoration( hintText: 'Search Artist ...', hintStyle: TextStyle( fontFamily: "Circular Sp", color: bannerTextColor, ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide.none, ), enabledBorder: const OutlineInputBorder( borderSide: BorderSide.none, ), suffixIcon: Visibility( visible: _searchController .text.isNotEmpty, child: InkWell( onTap: () { setState(() { _searchController .clear(); _filteredArtists = []; }); }, focusNode: FocusNode( skipTraversal: true), child: Padding( padding: const EdgeInsets.all( 8.0), child: Icon( Icons.close, color: bannerTextColor, size: 20, ), ), ), ), ), onChanged: (value) { setState(() { _filteredArtists = playlistStatistics .where((artist) => artist .artist.name .toLowerCase() .contains(value .toLowerCase())) .toList(); }); }, ), ), ), Visibility( visible: !_searchController .text.isNotEmpty && !_isSearching && !isLoading, child: IconButton( onPressed: () async { setState(() { sortByPercentageAscending = !sortByPercentageAscending; isLoading = true; playlistStatistics = _getArtistStatistics(); }); Future.delayed( const Duration(seconds: 2), () { setState(() { isLoading = false; }); }); }, icon: Icon( sortByPercentageAscending ? Icons.arrow_upward : Icons.arrow_downward, color: bannerTextColor, ), ), ), IconButton( onPressed: () { _launchURL( "https://open.spotify.com/playlist/${playlistObject.id}"); }, icon: Icon( Icons.link, color: bannerTextColor, ), ), ], ) ], ), ), ), ), ]; }, body: Column( children: [ isLoading ? Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ LoadingAnimationWidget.threeRotatingDots( color: Colors.white, size: 50), const SizedBox( height: 30, ), const SizedBox( width: 300, child: Text( "Please note that the waiting time will depend on the size of the playlist.", style: TextStyle( fontFamily: "Circular Sp", fontSize: 17.0, fontWeight: FontWeight.normal, color: Colors.white, ), textAlign: TextAlign.center, ), ), ], ), ) : Expanded( child: TransformableListView.builder( padding: const EdgeInsets.fromLTRB( 20, 10, 20, 20), getTransformMatrix: getTransformMatrix, itemBuilder: (context, index) { final GlobalKey cardKey = GlobalKey(); final ArtistResult artist = _filteredArtists.isNotEmpty ? _filteredArtists[index] : playlistStatistics[index]; return Padding( padding: const EdgeInsets.symmetric( vertical: 10.0), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: playlistDominantColorLight, spreadRadius: 2, blurRadius: 7.5, ), ], ), child: ExpansionTileCard( animateTrailing: true, trailing: const Icon( Icons.arrow_drop_down_rounded, color: Color(0xFFD2D2D2), ), baseColor: const Color(0xFF2a2a2a), // baseColor: playlistStatistics[index].color, expandedColor: const Color(0xFF383838), expandedTextColor: const Color(0xFFD2D2D2), shadowColor: playlistDominantColor, key: cardKey, leading: ClipOval( child: FadeInImage.assetNetwork( placeholder: "assets/load.png", image: artist .artist.images![0].url .toString(), width: 40.0, height: 40.0, ), ), // Dentro del itemBuilder del TransformableListView.builder title: Text( "${artist.artist.name} ➜ ${artist.percentage}% ", style: const TextStyle( fontFamily: "Circular Sp", fontSize: 17.0, fontWeight: FontWeight.normal, color: Color(0xFFD2D2D2), ), ), subtitle: Text( "${artist.total} songs ┃ Expand to see them.", style: const TextStyle( fontFamily: "Circular Sp", fontSize: 14.0, fontWeight: FontWeight.normal, color: Color(0xFFD2D2D2), ), ), children: [ Divider( thickness: 2.0, height: 1.0, color: playlistDominantColorLight, ), Padding( padding: const EdgeInsets.all(13), child: Container( decoration: BoxDecoration( border: Border.all( color: playlistDominantColorLight, width: 0.5, ), borderRadius: BorderRadius.circular( 10), color: const Color(0xFF454545), ), height: 150, child: SingleChildScrollView( padding: const EdgeInsets.all( 10), scrollDirection: Axis.vertical, child: Column( crossAxisAlignment: CrossAxisAlignment .start, children: [ for (var song in artist .tracks) ...[ Column( crossAxisAlignment: CrossAxisAlignment .start, children: [ GestureDetector( onTap: () async { _launchURL( "https://open.spotify.com/track/${song.id}"); }, child: Row( children: [ const Icon( Icons .music_note_rounded, color: Color( 0xFFD2D2D2), size: 18.0, ), const SizedBox( width: 5.0), Flexible( child: Text( "${song.name}", style: const TextStyle( fontFamily: "Circular Sp", fontSize: 14.0, fontWeight: FontWeight.normal, color: Color(0xFFD2D2D2), ), ), ), ], ), ), const SizedBox( height: 4.0), Text( "Artists: ${song.artists?.map((a) => a.name).join(', ')}", style: const TextStyle( fontFamily: "Circular Sp", fontSize: 14.0, fontWeight: FontWeight .normal, color: Colors .grey, ), ), const SizedBox( height: 4, ), Text( "Album: ${song.album?.name}", style: const TextStyle( fontFamily: "Circular Sp", fontSize: 14.0, fontWeight: FontWeight .normal, color: Colors .grey, ), ), const SizedBox( height: 8.0), ], ), ], ], ), ), ), ), ButtonBar( alignment: MainAxisAlignment .spaceAround, buttonHeight: 52.0, buttonMinWidth: 90.0, children: [ FloatingActionButton.extended( backgroundColor: playlistDominantColor, onPressed: () { _launchURL( "https://open.spotify.com/artist/${playlistStatistics[index].artist.id}"); }, label: Text( "Go to ${artist.artist.name}'s page", style: TextStyle( fontFamily: "Circular Sp", fontSize: 12.0, fontWeight: FontWeight.normal, color: bannerTextColor), ), icon: Icon( Icons .person_search_rounded, color: bannerTextColor, ), ), ], ), const SizedBox( height: 5, ), ], ), ), ); }, itemCount: _filteredArtists.isNotEmpty ? _filteredArtists.length : playlistStatistics.length, ), ), ], ), ), ), ); }, ), ); } } ```
hayribakici commented 9 months ago

@addreeh You can check out the wiki-page, where all implemented endpoints are listed.

addreeh commented 9 months ago

ok, thank you, finally can you tell me if the way i get all the tracks of the playlist is the correct?

hayribakici commented 9 months ago

I tried your app out, and retrieving each track and having a statistic like that, is also the only way I would come up with. But I think, you could optimize some of your for loops, for example

...
int offset = 0;
int limit = 100;

while (true) {
try {
  var tracks = await spotifyApi.playlists
      .getTracksByPlaylistId(playlistObject?.id ?? '')
      .getPage(limit, offset);
  if (tracks.items != null && tracks.items!.isNotEmpty) {
    try {
      songs.addAll(tracks!.items); //<-- just use addAll instead of another for loop.
      offset += limit;
    } catch (e) {
      offset = songs.length;
    }

And you also could play around with the dart collection api. It has some methods that could make you remove some of your for loops.

addreeh commented 9 months ago

I will try it, thank you.

hayribakici commented 9 months ago

@addreeh can this issue be closed?

addreeh commented 9 months ago

Sorry, I didnt have time to try those changes, but yes, can be clossed as completed. Thanks.