Apple Music le da dos IDs distintos al mismo álbum
Esta app luego se renombró a Deep Cut Atlas. Se la llama “Discoverer” a lo largo de este texto, porque así se llamaba el día en que ocurrió esto.
La función de esta semana en esta app de descubrimiento es el camino de “me gustó”: se está viendo un álbum guardado para revisar más tarde, se toca “Agregar a la biblioteca,” y la app ofrece de inmediato más lanzamientos de ese mismo artista para poner en cola a continuación. Agregar, y luego mantener el impulso. Construirla enseñó algo sobre MusicKit que no se esperaba, y es el tipo de cosa que rompería en silencio una implementación ingenua.
El flujo
Al tocar “Agregar a la biblioteca” en la tarjeta de un álbum ocurren tres cosas: las canciones del álbum entran a la biblioteca, esas mismas canciones salen de la lista “Por revisar” (ya se atendió, no debería quedarse ahí dando vueltas), y aparece una hoja deslizante: “Más de Khruangbin.” Esa hoja es una lista de otros lanzamientos del artista, los más nuevos primero, filtrados a los tipos de álbum/EP/sencillo/compilado que interesan, y, crucialmente, sin mostrar nada que ya se tenga. Se toca uno, entra a la lista de reproducción, la hoja se cierra. ¿No se quiere ninguno? Se desliza para descartarlo.
Los primeros dos pasos reutilizaron métodos de servicio que ya existían. La hoja es donde se puso interesante.
”No me muestres lo que ya tengo”
Ese filtro suena trivial. Están los lanzamientos del catálogo del artista, está la biblioteca del usuario: se resta una de la otra. El instinto inicial fue comparar por id, tal como se deduplicaría cualquier otra cosa.
Eso no funciona. MusicKit le da identificadores distintos a la copia de catálogo de un álbum y a la copia de biblioteca de ese mismo álbum. El álbum que está en la biblioteca y el álbum que se acaba de obtener de una búsqueda en el catálogo son, en lo que respecta a su MusicItemID, dos objetos sin relación. Entonces una verificación de pertenencia a un conjunto basada en ids no encuentra ninguna coincidencia y termina mostrando álbumes que ya se tienen.
Una vez que se ve, tiene sentido: el elemento de biblioteca es una copia personal con su propio ciclo de vida, el elemento de catálogo es el listado público de la tienda, pero no es obvio hasta que sale mal. La solución es hacer coincidir algo estable entre ambos: una clave normalizada de título más 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)) }
Es una heurística, y quedó anotada como algo para validar en un dispositivo real. Una edición de lujo y una edición estándar comparten título; una remasterización tal vez no. Pero está mucho más cerca de ser correcta que una comparación de ids que está estructuralmente condenada a fallar.
Encontrar al artista, para empezar
Hay una complicación relacionada. Los grupos de la lista de reproducción identifican su álbum mediante una clave sintetizada, no un id de catálogo real, porque, como se explicó en la publicación sobre agrupación de álbumes, una canción de lista de reproducción no lleva consigo la identidad completa de su álbum. Así que para encontrar “más de este artista” tampoco se puede buscar al artista por id. Se busca en el catálogo por nombre, se toma el primer resultado, y se piden sus álbumes. La misma imprecisión (dos artistas pueden compartir nombre), la misma nota pendiente de verificar en un dispositivo real.
Se nota un patrón a lo largo de este proyecto: MusicKit funciona mejor cuando se cuenta con identificadores de catálogo reales, y buena parte del trabajo consiste en reconstruirlos a partir de los datos más sueltos que realmente se tienen a mano.
Construyendo a ciegas, todavía
Como con cada función hasta ahora, nada de esto corrió contra la biblioteca real mientras se construía, MusicKit no funciona en el simulador, y todavía se usa una cuenta de desarrollador gratuita. El servicio simulado devuelve un conjunto fijo de lanzamientos de Khruangbin, incluyendo deliberadamente el álbum que se “agregó” y abarcando algunos tipos y años, para que la lógica de excluir/filtrar/ordenar tenga algo real con qué trabajar. Veintidós pruebas unitarias, todas en verde, todas contra datos falsos. La verificación honesta (¿de verdad se filtra la biblioteca real?, ¿la búsqueda de artista encuentra al Khruangbin correcto?) queda pendiente para el día en que se conecte un teléfono con una cuenta paga detrás.
La conclusión
Si hay que quedarse con una sola cosa de todo esto: en MusicKit, “¿este álbum ya está en la biblioteca del usuario?” no es una pregunta de id. La identidad en un catálogo musical es más desordenada que la identidad en una base de datos propia, porque el mismo registro existe en dos lugares que no se conocen entre sí. Hay que hacer coincidir lo que es estable para los humanos (título y artista), no lo que es estable para un solo sistema.
Lecturas relacionadas
La ventana espejada mintió sobre que el teléfono estaba desbloqueado
Un arreglo de agrupación de una línea que ya estaba escrito, y tres muros entre eso y la prueba: aislamiento de actores, el alcance exclusivo a Mac de log stream, y una sesión espejada que parece desbloqueada cuando el dispositivo no lo está.
Álbumes de preestreno, y la solución que no pude verificar del todo
Los marcadores de posición 'Track N' de Apple se mostraban como si fueran datos reales. La única señal de detección que no pude confirmar se convirtió en la única señal de la que dejé de depender.
Un botón de suscripción, una API de MusicKit que nunca había usado, y una sheet que se cierra en silencio
musicSubscriptionOffer presenta la sheet nativa de Apple, y nada en la app se enteraría jamás de que se cerró. La acción correctamente codificada que igual era un callejón sin salida funcional.