19 - Biblioteca Volley - Recuperar un archivo Json de una API pública

Volley es una biblioteca HTTP que facilita y agiliza el acceso a Internet en aplicaciones nativas en Android.

Permite administrar respuestas con formato strings, JSON e imágenes.

La manera de incorporar Volley en un proyecto es agregar la siguiente dependencia al archivo build.gradle de la aplicación:

dependencies {
        ...
        implementation 'com.android.volley:volley:1.2.0'
    }

Nuestra aplicación debe acceder a internet por lo que debemos pedir dicho permiso en el archivo AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

Problema

El sitio de videos dailymotion.com provee una API para recuperar una serie de datos de sus videos.

El API tiene muchos parámetros para filtrar los videos que queremos acceder (país, duración, etc.), emplearemos la más sencilla:

https://api.dailymotion.com/videos?limit=100

Nos retorna un archivo JSON con el siguiente formato:

archivo Json Jetpack Compose

Luego sabemos también que se nos provee una imagen en miniatura del video con la siguiente dirección:

https://www.dailymotion.com/thumbnail/video/[atributo id almacenado en el archivo JSON]

Si queremos acceder al sitio directamente de 'dailymotion.com' para ver un video en particular podemos hacerlo con la URL:

https://www.dailymotion.com/video/[atributo id almacenado en el archivo JSON]

La aplicación debe recuperar el id, título y canal. Mostrar una lista de tarjetas con el título, canal y la miniatura de la imagen, si se hace clic sobre la tarjeta se debe abrir el navegador y mostrar el video respectivo.

  1. Crearemos el proyecto Compose21

  2. Agregamos el permiso para que nuestra aplicación pueda acceder a internet en el archivo AndroidManifest.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.tutorialesprogramacionya.basura46">
        <uses-permission android:name="android.permission.INTERNET" />
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.Basura46">
            <activity
                android:name=".MainActivity"
                android:exported="true"
                android:label="@string/app_name"
                android:theme="@style/Theme.Basura46.NoActionBar">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    
    
  3. Agregamos las dependencias de la biblioteca Volley y coil-kt (es una biblioteca de carga de imágenes para Android respaldada por Kotlin Coroutines):

    dependencies {
    
        implementation 'androidx.core:core-ktx:1.6.0'
        implementation 'androidx.appcompat:appcompat:1.3.1'
        implementation 'com.google.android.material:material:1.4.0'
        implementation "androidx.compose.ui:ui:$compose_version"
        implementation "androidx.compose.material:material:$compose_version"
        implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
        implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
        implementation 'androidx.activity:activity-compose:1.3.1'
        implementation 'com.android.volley:volley:1.2.0'
        implementation("io.coil-kt:coil-compose:1.3.2") 
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.3'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
        androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
        debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
    }
    
  4. Debemos obtener un resultado similar a:

    Volley Jetpack Compose

  5. El código fuente que debemos implementar es:

    package com.tutorialesprogramacionya.compose21
    
    import android.app.Activity
    import android.content.Intent
    import android.net.Uri
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.Image
    import androidx.compose.foundation.clickable
    import androidx.compose.foundation.layout.*
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.items
    import androidx.compose.material.*
    import androidx.compose.runtime.*
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.platform.LocalContext
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    import coil.compose.rememberImagePainter
    import com.android.volley.Request
    import com.android.volley.toolbox.JsonObjectRequest
    import com.android.volley.toolbox.Volley
    
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ListadoVideos(this)
            setContent {
                PantallaPrincipal()
            }
        }
    }
    
    data class Video(val id: String, val titulo: String, val canal: String)
    
    val videos = mutableStateListOf<Video>()
    
    fun ListadoVideos(activity: Activity) {
        val url = "https://api.dailymotion.com/videos?limit=100"
        val requestQueue = Volley.newRequestQueue(activity)
        val jsonObjectRequest = JsonObjectRequest(
            Request.Method.GET,
            url,
            null,
            { response ->
                val jsonArray = response.getJSONArray("list")
                videos.clear()
                for (i in 0 until jsonArray.length()) {
                    val registro = jsonArray.getJSONObject(i)
                    val id = registro.getString("id")
                    val titulo = registro.getString("title")
                    val canal = registro.getString("channel")
                    videos.add(Video(id, titulo, canal))
                }
            },
            { error ->
            }
        )
        requestQueue.add(jsonObjectRequest)
    }
    
    @Composable
    fun PantallaPrincipal() {
        val activity = LocalContext.current
        LazyColumn() {
            items(videos) { video ->
                Card(
                    elevation = 5.dp,
                    modifier = Modifier
                        .padding(10.dp)
                        .fillMaxWidth()
                        .clickable {
                            val intento = Intent(
                                Intent.ACTION_VIEW,
                                Uri.parse("https://www.dailymotion.com/video/${video.id}")
                            )
                            activity.startActivity(intento)
                        }
                ) {
                    Column() {
                        Text(
                            text = "Título: ${video.titulo}",
                            fontSize = 18.sp,
                            modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp)
                        )
                        Text(
                            text = "Canal:${video.canal}",
                            modifier = Modifier.padding(10.dp)
                        )
                        Image(
                            painter = rememberImagePainter("https://www.dailymotion.com/thumbnail/video/${video.id}"),
                            contentDescription = null,
                            modifier = Modifier.size(350.dp, 200.dp)
                        )
                    }
                }
            }
        }
    }
    

    Desde el método onCreate llamamos a la función ListadoVideos (no es una función composable):

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ListadoVideos(this)
            setContent {
                PantallaPrincipal()
            }
        }
    }
    

    Definimos un data class para luego definir una serie de objeto de dicha clase y almacenarlos en la variable videos, que al ser de tipo mutableStateListOf hace que cuando se modifique, Compose se encargue de actualizar la pantalla donde se haga referencia a la misma:

    data class Video(val id: String, val titulo: String, val canal: String)
    
    val videos = mutableStateListOf<Video>()
    

    La función ListadoVideos procede a crear un objeto de la clase JsonObjectRequest, dicha clase encapsula la petición HTTP y mediante getJSONArray parseamos el archivo recibido para recuperar cada elemento del arreglo.

    Con cada objeto almacenado en el archivo Json procedemos a crear ahora un objeto de la clase Video y almacenarlo posteriormente en la variable 'videos' (esto hará que se actualice la pantalla):

    fun ListadoVideos(activity: Activity) {
        val url = "https://api.dailymotion.com/videos?limit=100"
        val requestQueue = Volley.newRequestQueue(activity)
        val jsonObjectRequest = JsonObjectRequest(
            Request.Method.GET,
            url,
            null,
            { response ->
                val jsonArray = response.getJSONArray("list")
                videos.clear()
                for (i in 0 until jsonArray.length()) {
                    val registro = jsonArray.getJSONObject(i)
                    val id = registro.getString("id")
                    val titulo = registro.getString("title")
                    val canal = registro.getString("channel")
                    videos.add(Video(id, titulo, canal))
                }
            },
            { error ->
            }
        )
        requestQueue.add(jsonObjectRequest)
    }
    

    Por último nuestra función composable llama a la función LazyColumn y con la función items recuperamos cada objeto almacenado en la variable 'videos'.
    Creamos una tarjeta (Card) por cada video, y llamando a la función Text dos veces mostramos el título y el canal del video.

    Para mostrar la imagen del video llamamos a la función composable 'rememberImagePainter' que se encuentra en la biblioteca:

        implementation("io.coil-kt:coil-compose:1.3.2") 
    

    Por últimos cuando se hace clic en la tarjeta procedemos a crear un objeto de la clase Intent y abrimos el navegador con la dirección donde se encuentra el video propiamente dicho:

    @Composable
    fun PantallaPrincipal() {
        val activity = LocalContext.current
        LazyColumn() {
            items(videos) { video ->
                Card(
                    elevation = 5.dp,
                    modifier = Modifier
                        .padding(10.dp)
                        .fillMaxWidth()
                        .clickable {
                            val intento = Intent(
                                Intent.ACTION_VIEW,
                                Uri.parse("https://www.dailymotion.com/video/${video.id}")
                            )
                            activity.startActivity(intento)
                        }
                ) {
                    Column() {
                        Text(
                            text = "Título: ${video.titulo}",
                            fontSize = 18.sp,
                            modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp)
                        )
                        Text(
                            text = "Canal:${video.canal}",
                            modifier = Modifier.padding(10.dp)
                        )
                        Image(
                            painter = rememberImagePainter("https://www.dailymotion.com/thumbnail/video/${video.id}"),
                            contentDescription = null,
                            modifier = Modifier.size(350.dp, 200.dp)
                        )
                    }
                }
            }
        }
    }
    

Este proyecto lo puede descargar en un zip desde este enlace: Compose21.zip