1. Code
  2. Mobile Development
  3. Android Development

Crea una aplicación de reproductor de música con Anko

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.
Scroll to top

Spanish (Español) translation by Ana Paulina Figueroa (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

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.

Project creation wizardProject creation wizardProject creation wizard

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.

Activity configuration screenActivity configuration screenActivity configuration screen

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.

AV section of the Vector Asset StudioAV section of the Vector Asset StudioAV section of the Vector Asset Studio

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 ImageView para mostrar la carátula del álbum de la canción que esté siendo reproducida en ese momento.
  • Un widget ImageButton que permite que el usuario pause o reanude la canción.
  • Un widget ImageButton que permite que el usuario elija otra canción aleatoria.
  • Un widget TextView para mostrar el título de la canción.
  • Un widget TextView para 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:

View hierarchy of the music playerView hierarchy of the music playerView hierarchy of the music player

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:

A screenshot of the appA screenshot of the appA screenshot of the app

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!