HTTP Requests con Retrofit en Android

HTTP Requests con Retrofit en Android

Hola amigos 馃憢 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a realizar peticiones HTTP con Retrofit, la biblioteca m谩s popular y eficiente para consumir APIs REST en Android.

Al finalizar este tutorial tendr谩s una aplicaci贸n que incluir谩:

  • Configuraci贸n de Retrofit
  • Peticiones GET, POST, PUT, DELETE
  • Manejo de respuestas JSON
  • Interceptores para logging
  • Manejo de errores
  • RecyclerView con datos de API
  • Carga as铆ncrona con callbacks

馃煩 1. ¿Qu茅 es Retrofit?

Retrofit es una biblioteca de tipo-seguro para realizar peticiones HTTP en Android y Java. Convierte tu API REST en una interfaz Java, facilitando el consumo de servicios web.

馃煩 2. Ventajas de Retrofit

  • C贸digo limpio y f谩cil de mantener
  • Conversi贸n autom谩tica de JSON
  • Soporte para peticiones s铆ncronas y as铆ncronas
  • Manejo de errores integrado
  • Compatible con RxJava, Coroutines

馃煩 3. Crear el Proyecto

Creamos un nuevo proyecto en Android Studio con Empty Activity.

馃煩 4. Agregar Dependencias

馃搫 build.gradle (Module: app)

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.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 para logging
    implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
    
    // Gson
    implementation 'com.google.code.gson:gson:2.10.1'
}

馃煩 5. Agregar Permisos

馃搫 AndroidManifest.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:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:usesCleartextTraffic="true"
        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. Clase Modelo

馃搫 Post.java

public class Post {
    private int userId;
    private int id;
    private String title;
    private String body;

    public 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; }
}

馃煩 7. Interface de la API

馃搫 JsonPlaceholderApi.java

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

public interface JsonPlaceholderApi {

    // GET - Obtener todos los posts
    @GET("posts")
    Call<List<Post>> getPosts();

    // GET - Obtener un post por ID
    @GET("posts/{id}")
    Call<Post> getPost(@Path("id") int id);

    // GET - Obtener posts con query parameters
    @GET("posts")
    Call<List<Post>> getPostsByUser(@Query("userId") int userId);

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

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

    // PATCH - Actualizar parcialmente
    @PATCH("posts/{id}")
    Call<Post> patchPost(@Path("id") int id, @Body Post post);

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

馃煩 8. Cliente Retrofit

馃搫 RetrofitClient.java

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) {
            
            // Logging interceptor
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

            // OkHttpClient con timeouts
            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())
                    .build();
        }
        return retrofit;
    }

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

馃煩 9. Dise帽o XML

馃搫 activity_main.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"
    android:padding="16dp">

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

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

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

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

        <Button
            android:id="@+id/btnDelete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="DELETE"
            android:layout_marginLeft="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" />

</LinearLayout>

馃煩 10. Layout del Item

馃搫 res/layout/item_post.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">

        <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/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="Descripci贸n"
            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

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<>();

    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);
    }

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

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

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

        void bind(Post post) {
            txtId.setText("ID: " + post.getId());
            txtTitle.setText(post.getTitle());
            txtBody.setText(post.getBody());
        }
    }
}

馃煩 12. MainActivity

馃搫 MainActivity.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 btnGet, btnPost, btnDelete;
    ProgressBar progressBar;
    JsonPlaceholderApi api;

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

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

        btnGet.setOnClickListener(v -> getPosts());
        btnPost.setOnClickListener(v -> createPost());
        btnDelete.setOnClickListener(v -> deletePost(1));
    }

    private void initializeViews() {
        recyclerView = findViewById(R.id.recyclerView);
        btnGet = findViewById(R.id.btnGet);
        btnPost = findViewById(R.id.btnPost);
        btnDelete = findViewById(R.id.btnDelete);
        progressBar = findViewById(R.id.progressBar);
    }

    private void setupRecyclerView() {
        adapter = new PostAdapter();
        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 de conexi贸n: " + t.getMessage(), 
                    Toast.LENGTH_LONG).show();
            }
        });
    }

    private void createPost() {
        showProgress(true);

        Post newPost = new Post(1, "Mi nuevo post", "Este es el contenido del post");

        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 al crear: " + 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 deletePost(int id) {
        showProgress(true);

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

                if (response.isSuccessful()) {
                    Toast.makeText(MainActivity.this, 
                        "Post eliminado exitosamente", 
                        Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, 
                        "Error al eliminar: " + response.code(), 
                        Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<Void> 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 Principales

Anotaci贸nDescripci贸n
@GETPetici贸n GET
@POSTPetici贸n POST
@PUTPetici贸n PUT
@DELETEPetici贸n DELETE
@PATCHActualizaci贸n parcial
@PathVariable en la URL
@QueryPar谩metro de consulta
@BodyCuerpo de la petici贸n
@HeaderHeader personalizado

▶️ C贸mo Ejecutar

  1. Abre Android Studio
  2. Sincroniza las dependencias
  3. Presiona Run
  4. Prueba los botones GET, POST, DELETE

馃И Resultado Final

Aplicaci贸n funcional que consume una API REST, muestra datos en RecyclerView y realiza operaciones CRUD.

馃摜 Descargar Proyecto

馃憠 

馃檶 Gracias por Visitar mi Blog

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

❓ Preguntas Frecuentes

1. ¿Qu茅 es Retrofit?
Una biblioteca tipo-seguro para realizar peticiones HTTP que convierte tu API REST en una interfaz Java.

2. ¿C贸mo manejo errores en Retrofit?
Usa los callbacks onResponse y onFailure, y verifica response.isSuccessful().

3. ¿Necesito permisos especiales?
S铆, INTERNET en el AndroidManifest y usesCleartextTraffic="true" para HTTP.

4. ¿Puedo usar Retrofit con Coroutines?
S铆, Retrofit soporta suspend functions para uso con Kotlin Coroutines.

No hay comentarios:

Publicar un comentario