Crea una aplicación de reproductor de música con Anko
Spanish (Español) translation by Ana Paulina Figueroa (you can also view the original English article)



Un buen enfoque para dominar un nuevo lenguaje de programación o una biblioteca es intentar crear algo útil con ellos. En mi tutorial sobre cómo simplificar el desarrollo de Android con Anko, te presenté el lenguaje de dominio específico de Anko y las funciones auxiliares. Aunque tengo la seguridad de que te parecieron impresionantes, es posible que todavía tengas miedo de usar esto en aplicaciones grandes y complejas, ya que es algo muy diferente de las clases y métodos tradicionales de Android.
Así que hoy vamos a usar Kotlin y Anko para crear una aplicación de reproductor de música para Android; una que pueda seleccionar y reproducir canciones aleatorias del dispositivo del usuario de manera automática. Su interfaz de usuario razonablemente compleja, que tendrá varios widgets diferentes interactuando entre sí, debería ayudarte a comprender mejor cómo funcionan las aplicaciones de Anko.
Requisitos previos
Para poder seguir este tutorial paso a paso, necesitarás:
- la versión más reciente de Android Studio
- un teléfono o tableta con Android 5.0 o posterior.
- y unos cuantos álbumes en MP3
Si aún no lo has hecho, lee el siguiente tutorial antes de continuar:
1. Creación de un proyecto nuevo
Ejecuta Android Studio y presiona el botón Iniciar un nuevo proyecto de Android Studio para abrir el asistente de creación de proyectos. En la siguiente pantalla asígnale un nombre a tu aplicación y asegúrate de que el campo Incluir soporte para Kotlin esté marcado.



A continuación, orienta tu aplicación a la API nivel 21 o posterior y elige la plantilla Actividad vacía. Ya que no necesitaremos ningún archivo XML de diseño, asegúrate de anular la selección del campo Generar archivo de diseño.



Por último, presiona Finalizar para crear el proyecto.
2. Adición de dependencias
Para añadir Anko al proyecto, agrega la siguiente dependencia implementation en el archivo build.gradle del módulo app:
1 |
implementation 'org.jetbrains.anko:anko:0.10.1' |
Usaremos la corrutinas de Kotlin para realizar algunas operaciones de manera asíncrona, así que agrega una dependencia para ello a continuación.
1 |
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3' |
Ya que nuestro reproductor de música toca canciones de forma aleatoria, este necesita una lista de todas las canciones que están disponibles en el dispositivo. Con el fin de evitar implementar la lógica para la creación de dicha lista, agrega DroidMelody, una biblioteca que he creado específicamente para este tutorial, como la última dependencia. Ya que está aprobada y ha sido publicada por jcenter, que es el repositorio predeterminado de Android Studio, el procedimiento para agregarla no es diferente al de la adición de cualquier otra dependencia.
1 |
implementation 'com.progur.droidmelody:droidmelody:1.0.2' |
Además, vamos a necesitar algunos iconos relacionados con medios, así que abre Vector Asset Studio a continuación. En su interior, navega a la categoría AV y elige los iconos con los símbolos de reproducción, pausa y reproducción aleatoria.



En este punto, los siguientes archivos deberían estar presentes en la carpeta res/drawable de tu proyecto:
- ic_play_arrow_black_24dp.xml
- ic_pause_black_24dp.xml
- ic_shuffle_black_24dp.xml
3. Solicitud de permisos
La mayoría de los usuarios guardan sus canciones en medios de almacenamiento externo. Por lo tanto, en dispositivos con Android Marshmallow o posterior, necesitaremos solicitar explícitamente el permiso READ_EXTERNAL_STORAGE en tiempo de ejecución.
Sin embargo, antes de solicitar el permiso debes verificar si el usuario ya lo ha otorgado. Puedes hacerlo llamando al método ContextCompat.checkSelfPermission() dentro del método onCreate() de tu actividad. Si el permiso no se ha concedido, puedes solicitarlo llamando al método ActivityCompat.requestPermissions().
De acuerdo a esto, agrega el siguiente código:
1 |
if(ContextCompat.checkSelfPermission(this, |
2 |
Manifest.permission.READ_EXTERNAL_STORAGE) |
3 |
!= PackageManager.PERMISSION_GRANTED) { |
4 |
|
5 |
// Ask for the permission
|
6 |
ActivityCompat.requestPermissions(this, |
7 |
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), |
8 |
0) |
9 |
|
10 |
} else { |
11 |
// Start creating the user interface
|
12 |
createPlayer() |
13 |
}
|
Observa que el código anterior invoca a un método llamado createPlayer() si el permiso ya ha sido concedido. Crearemos ese método en el siguiente paso.
Después de solicitar el permiso, debes sobrescribir el método onRequestPermissionsResult() de la actividad para determinar si el usuario ha aceptado tu solicitud. De ser así, debes llamar nuevamente al método createPlayer(). De lo contrario, debes mostrar un mensaje de error usando el auxiliar longToast() de Anko y cerrar la aplicación.
1 |
override fun onRequestPermissionsResult(requestCode: Int, |
2 |
permissions: Array<out String>, |
3 |
grantResults: IntArray) { |
4 |
super.onRequestPermissionsResult(requestCode, |
5 |
permissions, grantResults) |
6 |
|
7 |
if(grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
8 |
createPlayer() |
9 |
} else { |
10 |
longToast("Permission not granted. Shutting down.") |
11 |
finish() |
12 |
}
|
13 |
}
|
4. Obtención de todas las canciones
Ahora es momento de definir el método createPlayer(), que será el responsable de la apariencia y la funcionalidad de tu aplicación.
1 |
private fun createPlayer() { |
2 |
// More code here
|
3 |
}
|
Dentro del método, lo primero que debes hacer es generar una lista de todas las canciones disponibles en el dispositivo. Dado que, como es de esperarse, se trata de una operación potencialmente tardada, puedes iniciarla en una nueva corrutina usando la función async().
Dentro de la corrutina, crea una nueva instancia de la clase SongFinder de DroidMelody y llama a su método prepare(), que usa la resolución de contenido de tu actividad para encontrar todas las canciones y colocarlas en una lista llamada allSongs. Una vez que el método termine, simplemente puedes devolver allSongs de la corrutina. El siguiente código te muestra cómo hacerlo:
1 |
val songsJob = async { |
2 |
val songFinder = SongFinder(contentResolver) |
3 |
songFinder.prepare() |
4 |
songFinder.allSongs |
5 |
}
|
La corrutina anterior se ejecuta de manera asíncrona. Para esperar su resultado, debes llamar al método await() dentro de otra corrutina creada mediante el uso de la función launch(). Ya que usaremos el resultado para crear la interfaz de usuario de nuestra aplicación, la nueva corrutina debería ejecutarse en el subproceso de la interfaz de usuario. Esto se especifica enviando kotlinx.coroutines.experimental.android.UI como un argumento a launch().
1 |
launch(kotlinx.coroutines.experimental.android.UI) { |
2 |
val songs = songsJob.await() |
3 |
|
4 |
// More code here
|
5 |
}
|
Ahora tienes una lista de objetos Song. Cada objeto Song tendrá varios detalles importantes sobre la canción a la que hace referencia, como su título, artista, carátula del álbum y URI.
5. Creación de un componente de Anko
De forma predeterminada, el DSL de Anko está disponible de manera directa solamente dentro del método onCreate() de una actividad. Para poder usarlo dentro del método createPlayer(), puedes hacer uso de la función UI() o crear un nuevo componente de Anko. En este tutorial emplearemos el segundo enfoque, ya que es más reutilizable.
Para crear un nuevo componente de Anko, debes extender la clase abstracta AnkoComponent y sobrescribir su método createView(), dentro del cual tendrás acceso al DSL.
1 |
val playerUI = object:AnkoComponent<MainActivity> { |
2 |
override fun createView(ui: AnkoContext<MainActivity>) |
3 |
= with(ui) { |
4 |
// DSL code here
|
5 |
}
|
6 |
}
|
6. Definición de la interfaz de usuario
Dado que nuestra aplicación es un reproductor de música aleatorio (no uno que pueda funcionar con listas de reproducción), este tendrá una interfaz de usuario ligeramente poco convencional. Estos son los widgets visibles que contendrá:
- Un widget
ImageViewpara mostrar la carátula del álbum de la canción que esté siendo reproducida en ese momento. - Un widget
ImageButtonque permite que el usuario pause o reanude la canción. - Un widget
ImageButtonque permite que el usuario elija otra canción aleatoria. - Un widget
TextViewpara mostrar el título de la canción. - Un widget
TextViewpara mostrar el nombre del artista de la canción.
De acuerdo a esto, agrega los siguientes campos al componente de Anko:
1 |
var albumArt: ImageView? = null |
2 |
|
3 |
var playButton: ImageButton? = null |
4 |
var shuffleButton:ImageButton? = null |
5 |
|
6 |
var songTitle: TextView? = null |
7 |
var songArtist: TextView? = null |
Además necesitaremos un RelativeLayout y un par de contenedores LinearLayout para colocar los widgets anteriores y para establecer relaciones entre ellos. El siguiente diagrama muestra la jerarquía de vista que crearemos a continuación:



Ya que el widget RelativeLayout se encuentra en la raíz de la jerarquía de vista, debes crearlo primero añadiendo el siguiente código dentro del método createView() del componente de Anko:
1 |
relativeLayout { |
2 |
backgroundColor = Color.BLACK |
3 |
|
4 |
// More code here
|
5 |
}
|
Observa que hemos usado la propiedad backgroundColor del widget para asignarle un color negro. Usaremos varias de estas propiedades a lo largo de este tutorial para que nuestra aplicación se vea mejor. Siéntete libre de cambiar sus valores para que se ajusten a tus preferencias.
Dentro del widget RelativeLayout, agrega el widget ImageView para la carátula del álbum. Este debería ocupar todo el espacio disponible en la pantalla, así que usa el método lparams() después de añadirlo y envíale la constante matchParent dos veces, una para el ancho y otra para la altura. Así es como puedes hacerlo:
1 |
albumArt = imageView { |
2 |
scaleType = ImageView.ScaleType.FIT_CENTER |
3 |
}.lparams(matchParent, matchParent) |
El método lparams(), como su nombre lo sugiere, te permite especificar los parámetros de diseño que deben asociarse con un widget. Utilizándolo, puedes especificar rápidamente detalles como los márgenes que debe tener un widget, sus dimensiones y su posición.
A continuación, crea un widget LinearLayout con orientación vertical llamando a la función verticalLayout() y agrégale los widgets TextView. El diseño debe colocarse en la parte inferior de la pantalla, por lo que debes llamar a la función alignParentBottom() mientras especificas sus parámetros de diseño.
1 |
verticalLayout { |
2 |
backgroundColor = Color.parseColor("#99000000") |
3 |
|
4 |
songTitle = textView { |
5 |
textColor = Color.WHITE |
6 |
typeface = Typeface.DEFAULT_BOLD |
7 |
textSize = 18f |
8 |
}
|
9 |
|
10 |
songArtist = textView { |
11 |
textColor = Color.WHITE |
12 |
}
|
13 |
|
14 |
// More code here
|
15 |
|
16 |
}.lparams(matchParent, wrapContent) { |
17 |
alignParentBottom() |
18 |
}
|
De manera similar, crea el widget LinearLayout con orientación horizontal llamando a la función linearLayout() y agrégale los dos widgets ImageButton. Asegúrate de usar la propiedad imageResource de los botones para especificar los iconos que deben mostrar. El siguiente código te muestra cómo hacerlo:
1 |
linearLayout { |
2 |
|
3 |
playButton = imageButton { |
4 |
imageResource = R.drawable.ic_play_arrow_black_24dp |
5 |
onClick { |
6 |
playOrPause() |
7 |
}
|
8 |
}.lparams(0, wrapContent, 0.5f) |
9 |
|
10 |
shuffleButton = imageButton { |
11 |
imageResource = R.drawable.ic_shuffle_black_24dp |
12 |
onClick { |
13 |
playRandom() |
14 |
}
|
15 |
}.lparams(0, wrapContent, 0.5f) |
16 |
|
17 |
}.lparams(matchParent, wrapContent) { |
18 |
topMargin = dip(5) |
19 |
}
|
Puedes ver que el código anterior tiene controladores de eventos clic para ambos widgets ImageButton. Dentro de los controladores hay llamadas a dos métodos con nombres intuitivos: playOrPause() y playRandom(). Los crearemos en los siguientes pasos.
En este punto hemos terminado de definir la apariencia de nuestra aplicación.
7. Reproducción de canciones
Nuestra aplicación todavía es incapaz de reproducir música. Arreglemos eso creando el método playRandom().
1 |
fun playRandom() { |
2 |
// More code here
|
3 |
}
|
Usaremos una instancia de la clase MediaPlayer para reproducir la música. Es un recurso más bien caro, y debería ser liberado cuando el usuario cierre la aplicación. Por lo tanto, debe definirse como un campo de la actividad (no en el componente de Anko) y debe liberarse dentro del método onDestroy() de dicha actividad.
1 |
private var mediaPlayer: MediaPlayer? = null |
2 |
|
3 |
override fun onDestroy() { |
4 |
mediaPlayer?.release() |
5 |
super.onDestroy() |
6 |
}
|
Dentro del método playRandom(), ahora podemos elegir una canción aleatoria de la lista de canciones que generamos anteriormente simplemente "barajando" la lista y eligiendo el primer elemento. Este enfoque no es muy eficiente, pero es muy conciso.
1 |
Collections.shuffle(songs) |
2 |
val song = songs[0] |
Ahora puedes inicializar el reproductor multimedia con el URI de la canción recién elegida. Además, usa el método setOnCompletionListener() para asegurarte de que una nueva canción aleatoria comience a reproducirse tan pronto como termine la canción actual.
1 |
mediaPlayer?.reset() |
2 |
mediaPlayer = MediaPlayer.create(ctx, song.uri) |
3 |
mediaPlayer?.setOnCompletionListener { |
4 |
playRandom() |
5 |
}
|
El contenido de los widgets ImageView y TextView también debe actualizarse para mostrar los detalles asociados con la nueva canción.
1 |
albumArt?.imageURI = song.albumArt |
2 |
songTitle?.text = song.title |
3 |
songArtist?.text = song.artist |
Finalmente, para comenzar a reproducir la canción, puedes llamar al método start() del reproductor multimedia. Este también sería el momento adecuado para actualizar el widget ImageButton y cambiar el icono "reproducción" a un icono "pausa".
1 |
mediaPlayer?.start() |
2 |
playButton?.imageResource = R.drawable.ic_pause_black_24dp |
8. Pausa y reanudación
En un paso anterior, invocamos a un método llamado playOrPause() dentro del controlador de clics de uno de los widgets ImageButton. Defínelo como otro método del componente de Anko.
1 |
fun playOrPause() { |
2 |
// More code here
|
3 |
}
|
La lógica que debes implementar dentro de este método debería ser bastante evidente. Si el reproductor multimedia ya está reproduciendo una canción, lo cual puedes verificar usando la propiedad isPlaying, llama a su método pause() y muestra el icono "reproducción" en el ImageButton. De lo contrario, llama al método start() y muestra el icono "pausa".
1 |
val songPlaying: Boolean? = mediaPlayer?.isPlaying |
2 |
|
3 |
if(songPlaying == true) { |
4 |
mediaPlayer?.pause() |
5 |
playButton?.imageResource = R.drawable.ic_play_arrow_black_24dp |
6 |
} else { |
7 |
mediaPlayer?.start() |
8 |
playButton?.imageResource = R.drawable.ic_pause_black_24dp |
9 |
}
|
Ahora nuestro componente de Anko está listo.
9. Visualización del componente de Anko
No basta con crear un componente de Anko. También debes asegurarte de renderizarlo llamando a su método setContentView() y enviándole una actividad. Por ahora puedes enviarle la actividad principal.
De manera opcional, si quieres que la aplicación comience a reproducir una canción tan pronto como el usuario la abra, puedes volver a llamar al método playRandom() ahora.
1 |
playerUI.setContentView(this@MainActivity) |
2 |
playerUI.playRandom() |
Nuestra aplicación está lista. Si la ejecutas en un dispositivo que tenga uno o más archivos MP3 con etiquetas ID3 que tengan un formato adecuado y carátulas de álbum incrustadas, deberías ver algo similar a esto:



Conclusión
En este tutorial aprendiste cómo crear una aplicación de reproductor de música con una compleja jerarquía de vista usando solamente Anko y Kotlin. Al hacer eso, trabajaste con varias funciones avanzadas de ambas herramientas, como las corrutinas y los auxiliares de parámetros de diseño. También aprendiste de qué manera puedes lograr que el código de Anko sea más modular y reutilizable mediante la creación de componentes de Anko.
¡No dudes en añadir más funcionalidad a la aplicación o modificar su apariencia para darle un toque personal!
Y mientras estás aquí, ¡echa un vistazo a algunas de nuestras otras publicaciones sobre Kotlin y sobre la codificación de aplicaciones Android!


SDK de AndroidConsejo rápido: escribe código más claro con las conversiones SAM de KotlinAshraff Hathibelagal

SDK de AndroidJava frente a Kotlin: ¿deberías usar Kotlin para el desarrollo en Android?Jessica Thornsby

SDK de AndroidIntroducción a los componentes de la arquitectura de AndroidTin Megali

SDK de Android¿Qué son las aplicaciones instantáneas de Android?Jessica Thornsby

SDK de AndroidCómo crear una aplicación de chat de Android usando FirebaseAshraff Hathibelagal





