Tutorial y ejemplos de Android Retrofit.

¿Qué es Retrofit? Bueno, Retrofit es un cliente HTTP de tipo seguro creado por Square Inc.

El papel de Retrofit es convertir su API HTTP en una interfaz Java:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

En esta clase veremos Retrofit y ejemplos de Retrofit con respecto al desarrollo de android. Veremos cómo podemos utilizarlo para hablar con webservices vía HTTP de una manera sencilla.

¿Cuáles son los requisitos de Retrofit?

Retrofit tiene unos requisitos muy generosos:

  1. Java 7 y superior.
  2. Android 2.3 y superior.

¿Cómo instalo Retrofit?

Retrofit es una librería cliente HTTP de terceros para Android y Java. Por lo tanto, necesita ser instalado en su proyecto.

Si estás buscando una clase de red que sea estándar y esté incluida en el framework de android, entonces mira HttpURLConnection.

De lo contrario, Retrofit puede ser instalado de tres maneras:

1. Como un jar.

Básicamente descargas el jar de Retrofit y lo añades a tu android studio como una librería jar.

Por ejemplo yo he utilizado retrofit 2.4.0 que se puede descargar desde aquí.

Si no, puedes comprobar la última versión aquí.

2. A través de maven

Maven, también conocido como Apache Maven, es una herramienta de automatización de la construcción utilizada principalmente para proyectos Java.

Esto le da dos funciones principales:

  1. Describir cómo se construye un proyecto.
  2. Describir las dependencias del proyecto.

Bien podemos describir Retrofit como nuestra dependencia en un proyecto java.

<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>retrofit</artifactId>
  <version>2.4.0</version>
</dependency>

3. A través de Gradle

Gradle es un sistema de construcción utilizado por android studio.

Si estás creando un proyecto android, lo más probable es que utilices este método para instalar Retrofit. Esto se debe a que es probable que uses Android Studio ya que es el IDE oficial de desarrollo para android. Y android studio utiliza el sistema de construcción gradle.

En ese caso tendrás que ir a tu build.gradle a nivel de aplicación y añadir la siguiente declaración en tu DSL de dependencias:

implementation 'com.squareup.retrofit2:retrofit:2.4.0'

Common Retrofit Interfaces,Classes and Methods.

1. Llama a

Esta es una interfaz de Retrofit que representa una invocación de un método de Retrofit que envía una solicitud a un servidor web y devuelve una respuesta. Como su nombre indica, representa básicamente una llamada HTTP que se realiza.

Cada llamada producirá su propio par de solicitud y respuesta HTTP. Puedes hacer múltiples llamadas con los mismos parámetros. Sin embargo, se utiliza clon para lograr eso. Puedes usar esto para implementar el polling o para reintentar una llamada fallida.

Cuando se hacen llamadas HTTP, se pueden hacer de forma sincrónica o asincrónica. Para hacer llamadas sincrónicas se utiliza el método execute(). Sin embargo, para hacer llamadas asíncronas se utiliza el método enqueue().

Si quieres cancelar cualquier llamada, utiliza el método cancel().

Esta es su definición:

public interface Call<T> extends Cloneable

Ten en cuenta que en este caso T representa el tipo de un cuerpo de respuesta exitoso.

2. enqueue()

Este es un método que pertenece a la interfaz Call<T>. Hablamos de que esa interfaz representa una llamada HTTP que haces.

Y normalmente puedes hacer una llamada o petición sincrónica o asincrónica. Si quieres hacer una llamada asíncrona utilizas este método.

Aquí está su definición:

public abstract void enqueue(retrofit2.Callback<T> callback)

Este método enviará de forma asíncrona tu petición y notificará el callback de su respuesta en caso de que se produzca un error al hablar con el servidor, al crear la petición o al procesar la respuesta.

Aquí hay un ejemplo de uso simple:

        Call<List<Spacecraft>> call = myAPIService.getSpacecrafts();
        call.enqueue(new Callback<List<Spacecraft>>() {

            @Override
            public void onResponse(Call<List<Spacecraft>> call, Response<List<Spacecraft>> response) {
                myProgressBar.setVisibility(View.GONE);
                populateRecyclerView(response.body());
            }
            @Override
            public void onFailure(Call<List<Spacecraft>> call, Throwable throwable) {
                myProgressBar.setVisibility(View.GONE);
                Toast.makeText(MainActivity.this, throwable.getMessage(), Toast.LENGTH_LONG).show();
            }
        });

3. Callback

Se trata de una interfaz que define los métodos responsables de comunicar las respuestas de un servidor o las peticiones fuera de línea.

Esta es su definición:

public interface Callback<T>

T en lo anterior representa un tipo de cuerpo de respuesta exitoso.

Normalmente se llama a un solo método en respuesta a una petición determinada. Ese método se ejecutará utilizando el ejecutor de llamadas de retorno de Retrofit. Si no se especifica ninguno entonces se utilizan los siguientes valores por defecto:

  1. Android: Las devoluciones de llamada se ejecutan en el hilo principal (UI) de la aplicación.
  2. JVM: Los callbacks se ejecutan en el hilo de fondo que ha realizado la petición.

Este es un ejemplo de uso:

new Callback<List<Spacecraft>>() {

            @Override
            public void onResponse(Call<List<Spacecraft>> call, Response<List<Spacecraft>> response) {
                myProgressBar.setVisibility(View.GONE);
                populateRecyclerView(response.body());
            }
            @Override
            public void onFailure(Call<List<Spacecraft>> call, Throwable throwable) {
                myProgressBar.setVisibility(View.GONE);
                Toast.makeText(MainActivity.this, throwable.getMessage(), Toast.LENGTH_LONG).show();
            }
        }

4. Adaptación

Es una clase que se encarga de adaptar una interfaz Java a las llamadas HTTP utilizando anotaciones en los métodos declarados para definir cómo se realizan las peticiones.

Esta es su definición

public final class Retrofit extends Object

Para utilizarla necesitarás crear su instancia. Sin embargo, lo haces usando el constructor y pasas tu interfaz a create para generar una implementación. Por ejemplo:

   Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("https://api.example.com/")
       .addConverterFactory(GsonConverterFactory.create())
       .build();

   MyApi api = retrofit.create(MyApi.class);
   Response<User> user = api.getUser().execute();

O puedo crear una simple clase fábrica que me devuelva sus instancias:

    static class RetrofitClientInstance {

        private static Retrofit retrofit;
        private static final String BASE_URL = "https://raw.githubusercontent.com/";

        public static Retrofit getRetrofitInstance() {
            if (retrofit == null) {
                retrofit = new Retrofit.Builder()
                        .baseUrl(BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
            }
            return retrofit;
        }
    }

Luego uso esa clase de esta manera:

        MyAPIService myAPIService = RetrofitClientInstance.getRetrofitInstance().create(MyAPIService.class);

Ejemplo de películas Kotlin Android Retrofit

En este ejemplo verás cómo obtener una lista de películas del sitio web IMDB y renderizarla en un recyclerview. Los elementos se obtienen a través de Retrofit. Las imágenes se renderizan a través de Picasso.

Aquí está la imagen de demostración:

Paso 1: Crear el proyecto

Comienza creando un proyecto vacío de Android Studio.

Paso 2: Dependencias

Primero añadiremos Retrofit como una dependencia, junto con el convertidor-gson de Retrofit:

    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitConverterGsonVer"

También añadiremos dependencias para RecyclerView y CardView:

    implementation "androidx.recyclerview:recyclerview:$supportVer"
    implementation "androidx.cardview:cardview:$supportVer"

También añadiremos la dependencia para Picasso, nuestro cargador de imágenes:

    implementation "com.squareup.picasso:picasso:$picassoVersion"

Paso 3: Diseñar el Layout

A continuación diseñaremos nuestros layouts. Tendremos dos diseños:

(a). list\row.xml

Esto se inflará en una sola fila en nuestro recyclerview. Tendremos un cardview con imagen y texto:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="240dp"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="#fff"
        card_view:cardCornerRadius="2dp"
        card_view:contentPadding="10dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <ProgressBar
                android:id="@+id/progress_bar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:visibility="gone" />

            <ImageView
                android:id="@+id/image_view_movie"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:scaleType="fitXY" />

            <TextView
                android:id="@+id/movie_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginStart="10dp"
                android:textColor="@android:color/black"
                android:textSize="18sp"
                android:textStyle="bold" />

        </LinearLayout>

    </androidx.cardview.widget.CardView>

</LinearLayout>

b). activity_main.xml

En nuestro layout MainActivity añadiremos un recyclerview que renderizará nuestra lista de elementos:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.developers.usingretrofit.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/movie_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:layout_marginEnd="5dp"
        android:layout_marginStart="5dp" />
</LinearLayout>

Paso 4: Crear constantes

Una constante es una variable cuyo valor no cambia. Necesitaremos este tipo de variables, por ejemplo, con nuestra URL base y la URL de la imagen en nuestro proyecto. Crea un archivo llamado Constants.kt y añade el siguiente código:

Constantes.kt

class Constants {

    companion object {
        @JvmField
        val BASE_URL = "https://api.themoviedb.org/3/movie/";
        @JvmField
        val IMAGE_BASE_URL = "http://image.tmdb.org/t/p/w185/"
    }

}

Paso 5: Crear clases modelo

Tendremos dos clases modelo:

(a). MovieResult.kt

Esta es una clase que representará una llamada de resultado de película. Esta clase modelo tendrá propiedades como página, total de resultados, total de páginas así como la lista de resultados:

import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

data class MovieResult(@SerializedName("page")
                  @Expose
                  var page: Int? = null,
                  @SerializedName("total_results")
                  @Expose
                  var totalResults: Int? = null,
                  @SerializedName("total_pages")
                  @Expose
                  var totalPages: Int? = null,
                  @SerializedName("results")
                  @Expose
                  var results: List<Result>? = null)

(b). Resultado.kt

Esta clase de resultado representa una sola película así como sus propiedades como título, popularidad, IDs de género, etc:


import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

data class Result(
        @SerializedName("vote_count")
        @Expose
        var voteCount: Int? = null,
        @SerializedName("id")
        @Expose
        var id: Int? = null,
        @SerializedName("video")
        @Expose
        var video: Boolean? = null,
        @SerializedName("vote_average")
        @Expose
        var voteAverage: Double? = null,
        @SerializedName("title")
        @Expose
        var title: String? = null,
        @SerializedName("popularity")
        @Expose
        var popularity: Double? = null,
        @SerializedName("poster_path")
        @Expose
        var posterPath: String? = null,
        @SerializedName("original_language")
        @Expose
        var originalLanguage: String? = null,
        @SerializedName("original_title")
        @Expose
        var originalTitle: String? = null,
        @SerializedName("genre_ids")
        @Expose
        var genreIds: List<Int>? = null,
        @SerializedName("backdrop_path")
        @Expose
        var backdropPath: String? = null,
        @SerializedName("adult")
        @Expose
        var adult: Boolean? = null,
        @SerializedName("overview")
        @Expose
        var overview: String? = null,
        @SerializedName("release_date")
        @Expose
        var releaseDate: String? = null)

Paso 6: Crear un servicio API

Esto es simplemente una interfaz que contendrá un método que representa nuestra solicitud HTTP:

ApiInterface.kt


import com.developers.usingretrofit.model.MovieResult
import com.developers.usingretrofit.utils.Constants
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiInterface {

    @GET("popular")
    fun getMovies(@Query("api_key") key: String,
                  @Query("page") page: Int): Call<MovieResult>

    companion object Factory {

        fun create(): ApiInterface {

            val retrofit = Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(Constants.BASE_URL)
                    .build()

            return retrofit.create(ApiInterface::class.java);

        }

    }

}

Paso 7: Crear Adaptador

Para representar nuestra lista de películas en nuestro recyclerview necesitamos un adaptador. Cree uno exendiendo el RecyclerView.Adapter como se muestra a continuación:

MovieAdapter.kt

import android.content.Context
import android.net.Uri
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.developers.usingretrofit.R
import com.developers.usingretrofit.model.Result
import com.developers.usingretrofit.utils.Constants
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.list_row.view.*

class MovieAdapter(val context: Context, private val resultList: List<Result>?) : RecyclerView.Adapter<MovieAdapter.MyViewHolder>() {

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bindItems(resultList?.get(position))
    }

    override fun getItemCount(): Int {
        return resultList?.size!!
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.list_row, parent, false)
        return MyViewHolder(view)
    }

    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(result: Result?) {
            itemView.movie_title.text = result?.title
            val posterUri = Uri.parse(Constants.IMAGE_BASE_URL).buildUpon()
                    .appendEncodedPath(result?.posterPath)
                    .build()
            itemView.progress_bar.visibility = View.VISIBLE
            Picasso.with(itemView.context).load(posterUri.toString())
                    .into(itemView.image_view_movie, object : Callback {

                        override fun onError() {
                            //Show Error here
                        }

                        override fun onSuccess() {
                            itemView.progress_bar.visibility = View.GONE
                        }

                    })
        }
    }
}

Paso 8: Escribir el código de la MainActivity

Aquí está el código completo para la MainActivity:

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.GridLayoutManager
import com.developers.usingretrofit.adapter.MovieAdapter
import com.developers.usingretrofit.model.MovieResult
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val apiCall = ApiInterface.create()
        apiCall.getMovies(BuildConfig.TV_KEY, 1).enqueue(object : Callback<MovieResult> {
            override fun onFailure(call: Call<MovieResult>?, t: Throwable?) {
                showError(t?.message)
            }

            override fun onResponse(call: Call<MovieResult>?, response: Response<MovieResult>?) {
                val movieResponse = response?.body()
                val resultList = movieResponse?.results
                val layoutManager = GridLayoutManager(applicationContext,2)
                val adapter = MovieAdapter(applicationContext, resultList)
                movie_recycler_view.layoutManager = layoutManager
                movie_recycler_view.adapter = adapter
            }

        })
    }

    private fun showError(message: String?) {
        toast(message.toString())
    }

    fun Context.toast(msg: String) {
        Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show()
    }
}

Ejecutar

Copia el código o descárgalo en el siguiente enlace, construye y ejecuta.

Referencia

Aquí están los enlaces de referencia:

Número Enlace
1. Descargar Ejemplo
2. Seguir autor del código
3. Código: Licencia Apache 2.0

Categorizado en: