MusicKit Sample Code errors

In Xcode 13 beta 4, the Sample Code Using MusicKit to Integrate with Apple Music has compiler errors around the player.play() calls in handleTrackSelected and handlePlayButtonSelected. The error is "async call in a function that does not support concurrency / call can throw, but is not marked with try and the error is not handled"

Would it be possible to include a fix for this error in the sample code - or could somebody share a fix here in the forums?

Thanks so much!

Replies

Hello @RanLearns,

An updated version of the sample app is in the works. I hope to be able to publish it very soon.

Best regards,

  • Wonderful - thank you!

Add a Comment

Hi @RanLearns

I was in the same position as you when Apple released beta 4. I was able to fix the issues mainly around the play() being asynchronous now and can throw an error. I don't know if my approach is the standard, one but I'll share it nonetheless. 

Dividing the error into two parts - 

  • Trying to make an async call from a non-async function.
  • The call can throw errors, but the function is not handling the error.

For the first part, we can suffix the function with the async keyword and prefix the call with the await keyword. 

For example, `handlePlayButtonSelected() becomes -

  private func handlePlayButtonSelected() async {
   if !isPlaying {
    if !isPlaybackQueueSet {
     player.setQueue(with: album)
     isPlaybackQueueSet = true
    }
    await player.play()
   } else {
    player.pause()
   }
  }

We can make the function throw an error and prefix the call with the try keyword for the second part. So, handlePlayButtonSelected() finally looks like -

private func handlePlayButtonSelected() async throws {
   if !isPlaying {
    if !isPlaybackQueueSet {
     player.setQueue(with: album)
     isPlaybackQueueSet = true
    }
    try await player.play()
   } else {
    player.pause()
   }
  }

Similarly, `handlePlayButtonSelected() can be changed to - 

 private func handleTrackSelected(_ track: Track, loadedTracks: MusicItemCollection<Track>) async throws {
   player.setQueue(with: loadedTracks, startingAt: track)
   isPlaybackQueueSet = true
   try await player.play()
  }

Now, you'll get another error in view where you call these functions. First, in the list where you show the TrackCell, update the action handleTrackSelected(track, loaded tracks: loadedTracks) with the following - 

Task {
  try? await handleTrackSelected(track, loadedTracks: loadedTracks)
}

For the second error in the button in playButtonRow, update handlePlayButtonSelected with - 

Task {
  try? await handlePlayButtonSelected()
}

The third error is due to a change in the searchable modifier. Now, you've to specify a prompt. 

So, replace searchable("Albums", text: $searchTerm) with - 

.searchable(text: $searchTerm, prompt: "Albums")

There are a few warnings as well, mostly related to the use of detach. You can replace them with Task.detached

Lastly, you'll find deprecation warnings related to the setQueue(with:startingAt:) method. In Xcode 13, Beta 4 added a new instance property, queue

We can set this property by using the initializers of the class Queue. In AlbumDetailView.handleTrackSelected(_:loadedTracks:), you can set it as - 

private func handleTrackSelected(_ track: Track, loadedTracks: MusicItemCollection<Track>) async throws {
   player.queue = .init(for: loadedTracks, startingAt: track)
   isPlaybackQueueSet = true
   try await player.play()
  }

In handlePlayButtonSelected(), you can set it as - 

 private func handlePlayButtonSelected() async throws {
   if !isPlaying {
    if !isPlaybackQueueSet {
     player.queue = .init(arrayLiteral: album)
     isPlaybackQueueSet = true
    }
    try await player.play()
   } else {
    player.pause()
   }
  }

The last warning is related to the deprecation of the playbackStatus variable. The latest beta offers us with ObservableObject class MusicPlayer.State, and we'll use the playbackState instance property instead. I'm not sure about this one on how to go about observing the value of the playbackStatus, but here's my approach - 

 .task {
    isPlaying = player.state.playbackStatus == .playing
 }

With this, the project is error and warnings-free!

Hello @snuff4,

Thanks for taking the time to share how you addressed these issues with the sample app! Most of your suggestions are correct.

However, I just want to make a couple of points.

This part of your suggestion for handlePlayButtonSelected():

player.queue = .init(arrayLiteral: album)

is not how you should use this API, although it does work. What this initializer from the ExpressibleByArrayLiteral enables is the usage of the standard array syntax with square brackets:

player.queue = [album]

As for your last point to set the isPlaying variable like this:

.task {
    isPlaying = player.state.playbackStatus == .playing
 }

it does set it correctly at first, but it fails to properly observe the playback status, which you can do by setting up the player's state property as an @ObservedObject.

All of these changes will be included in the updated version of the sample app, which is on its way to being published.

I hope this helps.

Best regards,

Hello @RanLearns and @snuff4,

The updated version of our sample app for MusicKit is available here.

This version should work just fine with the iOS 15 beta 5 SDK.

I hope this helps.

Best regards,

@JoeKun thanks a lot. The app runs on my iPadPro (12.9-inch) (4th generation). Thank you for your time and effort. Really appreciate it!