Reusing the Browse experience in Muziqi


Another instance of reuse that recently came up in Muziqi was building the new flow to add songs to a playlist.

The Add Songs flow presents pretty much the same UI as the main view, but every view that you drill down to will have different elements. For example, the Album Detail view will let you Add All songs, and the tap on a song row will add it to the playlist instead of playing it back.

At first, I started building the flow by reconstructing a new set of Views that reused individual components like a SongRow. But that was a lot of work to rebuild the flow that existed in Home; I already had a flow that let you drill down to Albums, Artists etc. So I got to work on reusing the whole app hierarchy but in the smaller context of Add Song:

  • don't show the playlists
  • trigger adding to playlist from any detail view
  • hide any playback actions
  • hide any editing action

The obvious solution in SwiftUI also ended up working beautifully. Use an environment value that child views can check and customize themselves with:

enum BrowsingMode {
    case browse

    // PlaylistAddContentInfo has information and callbacks for adding to the playlist.
    case addToPlaylist(PlaylistAddContentInfo)
}

The top level App passes the .environment(\.browsingMode, .browse) which is the normal mode for browsing and playback. And when the sheet for Add Song is shown, the sheet is given .environment(\.browsing, .addToPlaylist(info)). In both cases the views are pretty much the same from that point on, with customization based on this environment variable. The only other major change I had to make was to use a different NavigationStack in the two cases.

I really enjoy how SwiftUI not only enables flexiblity and speed when building small child views, but also when building in the large like in this case.