O Apple Music dá dois IDs diferentes para o mesmo álbum
Esse app foi renomeado depois para Deep Cut Atlas. Ele é chamado de “Discoverer” ao longo do texto abaixo, porque era assim que se chamava no dia em que isso aconteceu.
A funcionalidade dessa semana no meu app de descoberta é o caminho do “gostei”: você está olhando um álbum que salvou para conferir depois, toca em Add to Library, e o app imediatamente oferece mais lançamentos do mesmo artista para você colocar na fila em seguida. Adiciona, e mantém o embalo. Construir isso me ensinou algo sobre o MusicKit que eu não esperava, e é o tipo de coisa que quebraria silenciosamente uma implementação ingênua.
O fluxo
Toque em “Add to Library” num card de álbum e três coisas acontecem: as faixas do álbum entram na sua biblioteca, essas mesmas faixas saem da playlist “To Check Out” (você já resolveu aquilo, não devia continuar lá), e uma sheet desliza para cima: “More from Khruangbin.” Essa sheet é uma lista de outros lançamentos do artista, mais recentes primeiro, filtrada pelos tipos álbum/EP/single/coletânea que interessam, e principalmente, sem mostrar nada que você já tem. Toque em um, ele vai para a playlist, a sheet fecha. Não quer nenhum? Arraste para descartar.
Os dois primeiros passos reaproveitaram métodos de serviço que eu já tinha. A sheet foi onde a coisa ficou interessante.
“Não me mostre o que eu já tenho”
Esse filtro parece trivial. Tenho os lançamentos do catálogo do artista, tenho a biblioteca do usuário, é só subtrair um do outro. Meu instinto foi comparar por id, do jeito que você removeria duplicatas de qualquer coisa.
Isso não funciona. O MusicKit dá identificadores diferentes para a cópia de catálogo de um álbum e a cópia de biblioteca do mesmo álbum. O álbum parado na sua biblioteca e o álbum que você acabou de puxar de uma busca no catálogo são, no que diz respeito ao MusicItemID deles, dois objetos sem relação nenhuma. Então uma checagem de pertencimento a um conjunto usando ids encontra zero sobreposição e mostra alegremente álbuns que você já tem.
Uma vez que você percebe, faz sentido: seu item de biblioteca é uma cópia pessoal com ciclo de vida próprio, o item de catálogo é a listagem pública da loja, mas isso não é óbvio até te morder. A correção é comparar por algo estável nos dois lados: uma chave normalizada de título mais artista.
let libraryKeys = Set(libraryAlbums.items.map {
albumKey(title: $0.title, artist: $0.artistName) // lowercased, trimmed
})
return catalogAlbums
.map(LibraryAlbum.init)
.filter { !libraryKeys.contains(albumKey(title: $0.title, artist: $0.artistName)) }
É uma heurística, e anotei como algo para validar num dispositivo real. Uma edição deluxe e uma edição padrão compartilham o título; um remaster talvez não. Mas está bem mais perto de certo do que uma comparação por id que está estruturalmente garantida a falhar.
Até achar o artista é complicado
Tem uma complicação relacionada. Meus grupos de playlist identificam o álbum deles por uma chave sintetizada, não um id real de catálogo, porque, como escrevi em o post sobre agrupamento de álbuns, uma faixa de playlist não carrega a identidade completa do seu álbum. Então, para achar “mais desse artista,” também não consigo buscar o artista por id. Busco no catálogo por nome, pego o primeiro resultado, e peço os álbuns dele. Mesma imprecisão (dois artistas podem compartilhar um nome), mesma anotação para mim mesmo de verificar num dispositivo real.
Estou percebendo um padrão nesse projeto: o MusicKit funciona melhor quando você tem identificadores reais de catálogo em mãos, e boa parte do trabalho é reconstruí-los a partir dos dados mais soltos que você de fato tem disponíveis.
Construindo às cegas, ainda
Como em toda funcionalidade até agora, nada disso rodou contra minha biblioteca real enquanto eu construía, o MusicKit não funciona no simulador, e eu ainda estou numa conta de desenvolvedor gratuita. O serviço simulado devolve um conjunto fixo de lançamentos do Khruangbin, incluindo de propósito o álbum que você “adicionou” e cobrindo alguns tipos e anos, para que a lógica de excluir / filtrar / ordenar tenha algo real para mastigar. Vinte e dois testes unitários, todos verdes, todos contra dados falsos. A verificação honesta, se minha biblioteca real de fato é filtrada corretamente, se a busca de artista encontra o Khruangbin certo, está na fila para o dia em que eu conectar um telefone com uma conta paga por trás.
A lição
Se você levar uma coisa disso: no MusicKit, “esse álbum já está na biblioteca do usuário?” não é uma pergunta de id. Identidade num catálogo de música é mais bagunçada do que identidade no seu próprio banco de dados, porque o mesmo registro existe em dois lugares que não sabem um do outro. Compare pelo que é estável para humanos, título e artista, não pelo que é estável para um sistema.
Leitura relacionada
A playlist que já tinha o nome certo
Um artefato de renomeação, uma API sem campo de autor pra atualizar, e um teste em dispositivo real provando que o bug já tinha se corrigido sozinho, fechado como aceitar como está.
Álbuns em destaque, dados reais do MusicKit, e um teste que mentiu sobre estar passando
Não existe uma flag isEssential - o Essentials que você vê é uma playlist de músicas. featuredAlbums é o sinal real, e uma mudança de fixture expôs um teste que ninguém tinha rodado de novo.
A aba Discover que ficou permanentemente presa cavando
Uma busca de artista que falhava nunca contava como checada, então um artista ruim se reselecionava para sempre - até um teste de várias rodadas achar a segunda proteção.
Você também pode achar útil
Proton Mail
E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisAdGuard para iOS
Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.
Como afiliado da AdGuard, ganho com compras qualificadas.
Saiba maisProton Pass
Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba mais