How to detect end of hasNextBatch?

Hello,

In MusicKit I would like to know how I can detect the end of hasNextBatch for a MusicItemCollection.

Let's say, I have a playlist with 100+ songs in my library.

Initially, I get 100 songs from the response and hasNextBatch is true.

If I use a while loop, every response includes the hasNextBatch = true. So I do not know when to stop or how many limited loop to make.

Is there way I can get the playlist loaded completely with all the songs loaded using nextBatch and stop when I have hit the end?

Accepted Reply

Hello @ashinthetray,

I cannot reproduce any issue with the playlist you suggested.

Here's some sample code that iteratively loads all the tracks of that playlist, one batch at a time.

var playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: "pl.u-76oN0LeIve30Yy")
playlistRequest.properties = [.tracks]

let playlistResponse = try await playlistRequest.response()
let playlist = playlistResponse.items[0]

print("loading all tracks for playlist \(playlist)")

var batchIndex = 0
var tracks = playlist.tracks ?? []
print("batch number \(batchIndex + 1) => \(tracks.count) tracks, hasNextBatch: \(tracks.hasNextBatch)")

repeat {
    if let nextBatchOfTracks = try await tracks.nextBatch() {
        tracks = nextBatchOfTracks
        batchIndex += 1
        print("batch number \(batchIndex + 1) => \(tracks.count) tracks, hasNextBatch: \(tracks.hasNextBatch)")
    } else {
        print("no next batch could be loaded")
        break
    }
} while tracks.hasNextBatch

print("done after \(batchIndex + 1) batches")

Here's the console output for this code:

loading all tracks for playlist Playlist(id: "pl.u-76oN0LeIve30Yy", name: "Insecure Official Playlist (Seasons 1-5)", curatorName: "Jordan")
batch number 1 => 100 tracks, hasNextBatch: true
batch number 2 => 100 tracks, hasNextBatch: true
batch number 3 => 100 tracks, hasNextBatch: true
batch number 4 => 100 tracks, hasNextBatch: true
batch number 5 => 4 tracks, hasNextBatch: false
done after 5 batches

Once you have the first batch of tracks, retrieved from Playlist's tracks property, you can call nextBatch() to get the second batch.

If you call nextBatch() again on the first batch, you'll get the exact same results. And that's to be expected, because MusicItemCollection is a value type (i.e. a struct), and won't mutate from under you when you call nextBatch(), because that method is not a mutating one. So just because you call nextBatch() won't change its internal state so that a second call would return a different result.

Instead, if you want to get the third batch, you need to call nextBatch() on the second batch, and so on and so forth.

So I think the main thing that makes this sample code work, which I'm guessing you didn't realize, is this:

    if let nextBatchOfTracks = try await tracks.nextBatch() {
        tracks = nextBatchOfTracks

i.e. the fact that we replace the value of the tracks local variable with the nextBatchOfTracks that was just loaded.

Nevertheless, while this is technically possible, as I just showed you, let me reiterate that we would generally hope that you never need to eagerly fetch all the possible next batches of a MusicItemCollection in this way.

What we would hope you do in your app is precisely what you said, to fetch the next batch as the users scroll further down. I'll leave it up to you to implement this behavior in your UI code using MusicItemCollection's nextBatch().

I will just give you one last tip: please read the documentation for MusicItemCollection's += operator.

I hope this helps.

Best regards,

Add a Comment

Replies

Hello @ashinthetray,

First of all, I should mention that you seem to be trying to use the API in a way that wasn't intended. The whole reason why collections can be paginated is to achieve a reasonable balance for various performance considerations, such as load times for the data, server load, and memory consumption. We would generally hope that you never need to eagerly fetch all the possible next batches of a MusicItemCollection.

So before I try to address your question more specifically, I need to ask you: what are you trying to do? and why do you think you have to eagerly fetch all the possible next batches of a MusicItemCollection? Hearing more about your use-case will either allow us to recommend a better and more performant way to achieve what you want, or can be used as valuable input for us to understand what new functionality we may want to add to MusicKit to enable implementing those use-cases more easily.

That said, in your while loop, you should eventually get to a point where the next batch collection is configured with hasNextBatch equal to false. I have tested this in the past in various contexts, and that's the behavior I saw.

But it sounds like you found an example where that isn't the case. In order for us to identify the root cause of this issue, we need you to file a new ticket on Feedback Assistant, including a sysdiagnose captured right after reproducing the issue where all the next batches in your while loop are configured with hasNextBatch equal to true. Including the id of your Playlist would also be helpful.

Thank you very much in advance.

Best regards,

  • Hi @JoeKun,

    Here is the use case: In Caset, there is an import playlist feature; some users have more than 100+ playlists how can I fetch all the playlists? Or even do it in a way that fetches nextBatch as the users scroll further down?

    Another use case: While displaying top songs for Artists, it seems like the hasNextBatch always returns as true and has no end so MusicItemCollection with songs will returned no matter how many requests I make.

    As for a playlists where hasNextBatch always returns as true even with a while loop, please try this.

Add a Comment

Hello @ashinthetray,

I cannot reproduce any issue with the playlist you suggested.

Here's some sample code that iteratively loads all the tracks of that playlist, one batch at a time.

var playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: "pl.u-76oN0LeIve30Yy")
playlistRequest.properties = [.tracks]

let playlistResponse = try await playlistRequest.response()
let playlist = playlistResponse.items[0]

print("loading all tracks for playlist \(playlist)")

var batchIndex = 0
var tracks = playlist.tracks ?? []
print("batch number \(batchIndex + 1) => \(tracks.count) tracks, hasNextBatch: \(tracks.hasNextBatch)")

repeat {
    if let nextBatchOfTracks = try await tracks.nextBatch() {
        tracks = nextBatchOfTracks
        batchIndex += 1
        print("batch number \(batchIndex + 1) => \(tracks.count) tracks, hasNextBatch: \(tracks.hasNextBatch)")
    } else {
        print("no next batch could be loaded")
        break
    }
} while tracks.hasNextBatch

print("done after \(batchIndex + 1) batches")

Here's the console output for this code:

loading all tracks for playlist Playlist(id: "pl.u-76oN0LeIve30Yy", name: "Insecure Official Playlist (Seasons 1-5)", curatorName: "Jordan")
batch number 1 => 100 tracks, hasNextBatch: true
batch number 2 => 100 tracks, hasNextBatch: true
batch number 3 => 100 tracks, hasNextBatch: true
batch number 4 => 100 tracks, hasNextBatch: true
batch number 5 => 4 tracks, hasNextBatch: false
done after 5 batches

Once you have the first batch of tracks, retrieved from Playlist's tracks property, you can call nextBatch() to get the second batch.

If you call nextBatch() again on the first batch, you'll get the exact same results. And that's to be expected, because MusicItemCollection is a value type (i.e. a struct), and won't mutate from under you when you call nextBatch(), because that method is not a mutating one. So just because you call nextBatch() won't change its internal state so that a second call would return a different result.

Instead, if you want to get the third batch, you need to call nextBatch() on the second batch, and so on and so forth.

So I think the main thing that makes this sample code work, which I'm guessing you didn't realize, is this:

    if let nextBatchOfTracks = try await tracks.nextBatch() {
        tracks = nextBatchOfTracks

i.e. the fact that we replace the value of the tracks local variable with the nextBatchOfTracks that was just loaded.

Nevertheless, while this is technically possible, as I just showed you, let me reiterate that we would generally hope that you never need to eagerly fetch all the possible next batches of a MusicItemCollection in this way.

What we would hope you do in your app is precisely what you said, to fetch the next batch as the users scroll further down. I'll leave it up to you to implement this behavior in your UI code using MusicItemCollection's nextBatch().

I will just give you one last tip: please read the documentation for MusicItemCollection's += operator.

I hope this helps.

Best regards,

Add a Comment