Retrofit + Gson en Android

Retrofit + Gson en Android

Hola amigos 馃憢 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a usar Retrofit con Gson, la combinaci贸n perfecta para consumir APIs REST convirtiendo autom谩ticamente JSON en objetos Java y viceversa.

Al finalizar este tutorial tendr谩s:

  • Configuraci贸n completa Retrofit + Gson
  • Peticiones GET, POST, PUT, DELETE
  • Conversi贸n autom谩tica JSON
  • Modelos con anotaciones Gson
  • Manejo de errores
  • RecyclerView con datos de API
  • Ejemplos pr谩cticos

馃煩 1. ¿Qu茅 es Retrofit + Gson?

Retrofit es una biblioteca HTTP type-safe que convierte tu API en una interfaz Java. Gson es el convertidor que transforma JSON en objetos Java autom谩ticamente.

馃煩 2. Ventajas de la Combinaci贸n

  • Conversi贸n autom谩tica JSON ↔ Java
  • Type-safe (detecci贸n de errores en compile-time)
  • C贸digo limpio y mantenible
  • Anotaciones @SerializedName para mapeo
  • Manejo de tipos complejos

馃煩 3. Crear el Proyecto

Creamos un nuevo proyecto en Android Studio con Empty Activity.

馃煩 4. Agregar Dependencias

馃搫 build.gradle (Module: app)

gradle
dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    implementation 'androidx.cardview:cardview:1.0.0'
    
    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    
    // OkHttp Logging
    implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
    
    // Gson
    implementation 'com.google.code.gson:gson:2.10.1'
}

馃煩 5. Agregar Permisos

馃搫 AndroidManifest.xml

xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

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

    <application
        android:usesCleartextTraffic="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

馃煩 6. Modelos con Gson

馃搫 Post.java

java
import com.google.gson.annotations.SerializedName;

public class Post {
    
    @SerializedName("userId")
    private int userId;
    
    @SerializedName("id")
    private int id;
    
    @SerializedName("title")
    private String title;
    
    @SerializedName("body")
    private String body;

    // Constructor vac铆o
    public Post() {}

    // Constructor para crear post
    public Post(int userId, String title, String body) {
        this.userId = userId;
        this.title = title;
        this.body = body;
    }

    // Getters
    public int getUserId() { return userId; }
    public int getId() { return id; }
    public String getTitle() { return title; }
    public String getBody() { return body; }

    // Setters
    public void setUserId(int userId) { this.userId = userId; }
    public void setId(int id) { this.id = id; }
    public void setTitle(String title) { this.title = title; }
    public void setBody(String body) { this.body = body; }
}

馃搫 User.java

java
import com.google.gson.annotations.SerializedName;

public class User {
    
    @SerializedName("id")
    private int id;
    
    @SerializedName("name")
    private String name;
    
    @SerializedName("username")
    private String username;
    
    @SerializedName("email")
    private String email;
    
    @SerializedName("phone")
    private String phone;

    public int getId() { return id; }
    public String getName() { return name; }
    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
}

馃煩 7. Interface API

馃搫 JsonPlaceholderApi.java

java
import java.util.List;
import retrofit2.Call;
import retrofit2.http.*;

public interface JsonPlaceholderApi {

    // GET - Lista de posts
    @GET("posts")
    Call<List<Post>> getPosts();

    // GET - Post espec铆fico
    @GET("posts/{id}")
    Call<Post> getPost(@Path("id") int id);

    // GET - Posts por usuario
    @GET("posts")
    Call<List<Post>> getPostsByUser(@Query("userId") int userId);

    // GET - Lista de usuarios
    @GET("users")
    Call<List<User>> getUsers();

    // POST - Crear post
    @POST("posts")
    Call<Post> createPost(@Body Post post);

    // PUT - Actualizar completo
    @PUT("posts/{id}")
    Call<Post> updatePost(@Path("id") int id, @Body Post post);

    // PATCH - Actualizaci贸n parcial
    @PATCH("posts/{id}")
    Call<Post> patchPost(@Path("id") int id, @Body Post post);

    // DELETE - Eliminar post
    @DELETE("posts/{id}")
    Call<Void> deletePost(@Path("id") int id);

    // POST con campos individuales
    @FormUrlEncoded
    @POST("posts")
    Call<Post> createPostForm(
        @Field("userId") int userId,
        @Field("title") String title,
        @Field("body") String body
    );
}

馃煩 8. Cliente Retrofit con Gson

馃搫 RetrofitClient.java

java
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;

public class RetrofitClient {

    private static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            
            // Configurar Gson
            Gson gson = new GsonBuilder()
                    .setLenient()
                    .create();

            // Logging interceptor
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

            // OkHttpClient
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .writeTimeout(30, TimeUnit.SECONDS)
                    .build();

            // Retrofit instance
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();
        }
        return retrofit;
    }

    public static JsonPlaceholderApi getApi() {
        return getClient().create(JsonPlaceholderApi.class);
    }
}

馃煩 9. Dise帽o XML

馃搫 activity_main.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Retrofit + Gson Demo"
        android:textSize="24sp"
        android:textStyle="bold"
        android:textColor="#2196F3"
        android:padding="16dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="8dp">

        <Button
            android:id="@+id/btnGetPosts"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="GET Posts"
            android:layout_margin="4dp" />

        <Button
            android:id="@+id/btnGetUsers"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="GET Users"
            android:layout_margin="4dp" />

        <Button
            android:id="@+id/btnCreate"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="POST"
            android:layout_margin="4dp" />

    </LinearLayout>

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

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="8dp" />

</LinearLayout>

馃煩 10. Layout del Item

馃搫 res/layout/item_post.xml

xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/txtId"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="ID: 1"
                android:textSize="12sp"
                android:textColor="#999999" />

            <TextView
                android:id="@+id/txtUserId"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="User: 1"
                android:textSize="12sp"
                android:textColor="#999999"
                android:layout_marginStart="16dp" />

        </LinearLayout>

        <TextView
            android:id="@+id/txtTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="T铆tulo"
            android:textSize="18sp"
            android:textStyle="bold"
            android:textColor="#000000"
            android:layout_marginTop="8dp" />

        <TextView
            android:id="@+id/txtBody"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Cuerpo del post"
            android:textSize="14sp"
            android:textColor="#666666"
            android:layout_marginTop="8dp"
            android:maxLines="3"
            android:ellipsize="end" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

馃煩 11. Adaptador

馃搫 PostAdapter.java

java
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.ViewHolder> {

    private List<Post> posts = new ArrayList<>();
    private OnItemClickListener listener;

    public interface OnItemClickListener {
        void onItemClick(Post post);
    }

    public PostAdapter(OnItemClickListener listener) {
        this.listener = listener;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_post, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Post post = posts.get(position);
        holder.bind(post, listener);
    }

    @Override
    public int getItemCount() {
        return posts.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView txtId, txtUserId, txtTitle, txtBody;

        ViewHolder(View itemView) {
            super(itemView);
            txtId = itemView.findViewById(R.id.txtId);
            txtUserId = itemView.findViewById(R.id.txtUserId);
            txtTitle = itemView.findViewById(R.id.txtTitle);
            txtBody = itemView.findViewById(R.id.txtBody);
        }

        void bind(Post post, OnItemClickListener listener) {
            txtId.setText("ID: " + post.getId());
            txtUserId.setText("User: " + post.getUserId());
            txtTitle.setText(post.getTitle());
            txtBody.setText(post.getBody());

            itemView.setOnClickListener(v -> listener.onItemClick(post));
        }
    }
}

馃煩 12. MainActivity

馃搫 MainActivity.java

java
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    PostAdapter adapter;
    Button btnGetPosts, btnGetUsers, btnCreate;
    ProgressBar progressBar;
    JsonPlaceholderApi api;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializeViews();
        setupRecyclerView();
        
        api = RetrofitClient.getApi();

        btnGetPosts.setOnClickListener(v -> getPosts());
        btnGetUsers.setOnClickListener(v -> getUsers());
        btnCreate.setOnClickListener(v -> createPost());
    }

    private void initializeViews() {
        recyclerView = findViewById(R.id.recyclerView);
        btnGetPosts = findViewById(R.id.btnGetPosts);
        btnGetUsers = findViewById(R.id.btnGetUsers);
        btnCreate = findViewById(R.id.btnCreate);
        progressBar = findViewById(R.id.progressBar);
    }

    private void setupRecyclerView() {
        adapter = new PostAdapter(post -> 
            Toast.makeText(this, "Post: " + post.getTitle(), Toast.LENGTH_SHORT).show()
        );
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
    }

    private void getPosts() {
        showProgress(true);

        Call<List<Post>> call = api.getPosts();
        call.enqueue(new Callback<List<Post>>() {
            @Override
            public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
                showProgress(false);

                if (response.isSuccessful() && response.body() != null) {
                    List<Post> posts = response.body();
                    adapter.setPosts(posts);
                    
                    Toast.makeText(MainActivity.this, 
                        "✓ Cargados: " + posts.size() + " posts", 
                        Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, 
                        "Error: " + response.code(), 
                        Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<List<Post>> call, Throwable t) {
                showProgress(false);
                Toast.makeText(MainActivity.this, 
                    "Error: " + t.getMessage(), 
                    Toast.LENGTH_LONG).show();
            }
        });
    }

    private void getUsers() {
        showProgress(true);

        Call<List<User>> call = api.getUsers();
        call.enqueue(new Callback<List<User>>() {
            @Override
            public void onResponse(Call<List<User>> call, Response<List<User>> response) {
                showProgress(false);

                if (response.isSuccessful() && response.body() != null) {
                    List<User> users = response.body();
                    
                    StringBuilder info = new StringBuilder();
                    for (User user : users) {
                        info.append(user.getName()).append("\n");
                    }
                    
                    Toast.makeText(MainActivity.this, 
                        "Usuarios: " + users.size(), 
                        Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<List<User>> call, Throwable t) {
                showProgress(false);
                Toast.makeText(MainActivity.this, 
                    "Error: " + t.getMessage(), 
                    Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void createPost() {
        showProgress(true);

        Post newPost = new Post(1, "Mi Post con Gson", "Creado usando Retrofit y Gson");

        Call<Post> call = api.createPost(newPost);
        call.enqueue(new Callback<Post>() {
            @Override
            public void onResponse(Call<Post> call, Response<Post> response) {
                showProgress(false);

                if (response.isSuccessful() && response.body() != null) {
                    Post createdPost = response.body();
                    Toast.makeText(MainActivity.this, 
                        "✓ Post creado con ID: " + createdPost.getId(), 
                        Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, 
                        "Error: " + response.code(), 
                        Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<Post> call, Throwable t) {
                showProgress(false);
                Toast.makeText(MainActivity.this, 
                    "Error: " + t.getMessage(), 
                    Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void showProgress(boolean show) {
        progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
    }
}

馃煩 13. Anotaciones Gson 脷tiles

java
// Mapear nombre diferente
@SerializedName("user_id")
private int userId;

// Nombre alternativo
@SerializedName(value = "email", alternate = {"e-mail", "emailAddress"})
private String email;

// Excluir del JSON
@Expose(serialize = false, deserialize = false)
private String password;

// Formato de fecha
@SerializedName("created_at")
private Date createdAt;

馃煩 14. Configurar Gson Custom

java
Gson gson = new GsonBuilder()
    .setLenient() // JSON flexible
    .setPrettyPrinting() // JSON formateado
    .setDateFormat("yyyy-MM-dd HH:mm:ss") // Formato fecha
    .excludeFieldsWithoutExposeAnnotation() // Solo @Expose
    .create();

馃煩 15. Comparaci贸n Convertidores

ConvertidorVelocidadTama帽oPopularidad
GsonMediaMedioAlta
MoshiR谩pidaPeque帽oMedia
JacksonR谩pidaGrandeAlta

▶️ C贸mo Ejecutar

  1. Abre Android Studio
  2. Copia el c贸digo
  3. Sync Gradle
  4. Presiona Run
  5. Prueba los botones GET, POST

馃И Resultado Final

App que consume API REST con conversi贸n autom谩tica JSON usando Retrofit + Gson.

馃摜 Descargar Proyecto

馃憠 

馃檶 Gracias por Visitar mi Blog

✔️ Comp谩rtelo
✔️ D茅jame un comentario
✔️ S铆gueme para m谩s contenido

❓ Preguntas Frecuentes

1. ¿Qu茅 hace @SerializedName?
Mapea el nombre del campo JSON al campo Java, 煤til cuando los nombres difieren.

2. ¿Retrofit funciona sin Gson?
S铆, pero Gson automatiza la conversi贸n JSON. Sin 茅l debes parsear manualmente.

3. ¿Puedo usar otro convertidor?
S铆, puedes usar Moshi, Jackson u otros con Retrofit.

4. ¿C贸mo manejo errores HTTP?
Verifica response.isSuccessful() y response.code() en el callback.

No hay comentarios:

Publicar un comentario