Hola amigos 👋 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a usar Retrofit con Kotlinx Serialization, la alternativa moderna a Gson que ofrece mejor rendimiento, type-safety y está optimizada para Kotlin.
Al finalizar este tutorial tendrás:
- Retrofit con Kotlinx Serialization
- Modelos con @Serializable
- Peticiones GET, POST, PUT, DELETE
- Conversión automática JSON
- Manejo de nombres personalizados
- RecyclerView con datos de API
- Ejemplos prácticos
🟩 1. ¿Qué es Kotlinx Serialization?
Kotlinx Serialization es la biblioteca oficial de JetBrains para serialización en Kotlin. Es más rápida que Gson, type-safe y genera código en compile-time.
🟩 2. Ventajas vs Gson
| CaracterÃstica | Kotlinx Serialization | Gson |
|---|---|---|
| Velocidad | Más rápida | Rápida |
| Type-safety | SÃ | No |
| Multiplatform | SÃ | No |
| Compile-time | Sà | Reflexión |
| Tamaño | Menor | Mayor |
🟩 3. Crear el Proyecto
Creamos un nuevo proyecto en Android Studio con Empty Activity (Kotlin).
🟩 4. Agregar Plugin y Dependencias
📄 build.gradle (Project)
plugins {
id 'com.android.application' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.20' apply false
}📄 build.gradle (Module: app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization'
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
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'
// Kotlinx Serialization
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2'
implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0'
// OkHttp Logging
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
}🟩 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: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 @Serializable
📄 Post.kt
package com.example.retrofitserializationapp
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Post(
@SerialName("userId")
val userId: Int,
@SerialName("id")
val id: Int = 0,
@SerialName("title")
val title: String,
@SerialName("body")
val body: String
)📄 User.kt
package com.example.retrofitserializationapp
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
@SerialName("id")
val id: Int,
@SerialName("name")
val name: String,
@SerialName("username")
val username: String,
@SerialName("email")
val email: String,
@SerialName("phone")
val phone: String? = null,
@SerialName("website")
val website: String? = null
)📄 Comment.kt
package com.example.retrofitserializationapp
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Comment(
@SerialName("postId")
val postId: Int,
@SerialName("id")
val id: Int,
@SerialName("name")
val name: String,
@SerialName("email")
val email: String,
@SerialName("body")
val body: String
)🟩 7. Interface API
📄 JsonPlaceholderApi.kt
package com.example.retrofitserializationapp
import retrofit2.Response
import retrofit2.http.*
interface JsonPlaceholderApi {
// GET - Lista de posts
@GET("posts")
suspend fun getPosts(): Response<List<Post>>
// GET - Post especÃfico
@GET("posts/{id}")
suspend fun getPost(@Path("id") id: Int): Response<Post>
// GET - Posts por usuario
@GET("posts")
suspend fun getPostsByUser(@Query("userId") userId: Int): Response<List<Post>>
// GET - Lista de usuarios
@GET("users")
suspend fun getUsers(): Response<List<User>>
// GET - Comentarios de un post
@GET("posts/{id}/comments")
suspend fun getComments(@Path("id") postId: Int): Response<List<Comment>>
// POST - Crear post
@POST("posts")
suspend fun createPost(@Body post: Post): Response<Post>
// PUT - Actualizar completo
@PUT("posts/{id}")
suspend fun updatePost(@Path("id") id: Int, @Body post: Post): Response<Post>
// PATCH - Actualización parcial
@PATCH("posts/{id}")
suspend fun patchPost(@Path("id") id: Int, @Body post: Post): Response<Post>
// DELETE - Eliminar post
@DELETE("posts/{id}")
suspend fun deletePost(@Path("id") id: Int): Response<Unit>
}🟩 8. Cliente Retrofit
📄 RetrofitClient.kt
package com.example.retrofitserializationapp
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import java.util.concurrent.TimeUnit
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
isLenient = true
}
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
val api: JsonPlaceholderApi = retrofit.create(JsonPlaceholderApi::class.java)
}🟩 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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Retrofit + Kotlinx Serialization"
android:textSize="20sp"
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="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="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="Create"
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" />
<TextView
android:id="@+id/txtInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="14sp"
android:textColor="#666666" />
<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 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"
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginTop="8dp"
android:maxLines="3"
android:ellipsize="end" />
</LinearLayout>
</androidx.cardview.widget.CardView>🟩 11. Adaptador
📄 PostAdapter.kt
package com.example.retrofitserializationapp
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class PostAdapter(
private var posts: List<Post> = emptyList(),
private val onItemClick: (Post) -> Unit
) : RecyclerView.Adapter<PostAdapter.ViewHolder>() {
fun updatePosts(newPosts: List<Post>) {
posts = newPosts
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_post, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(posts[position])
}
override fun getItemCount(): Int = posts.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val txtId: TextView = itemView.findViewById(R.id.txtId)
private val txtUserId: TextView = itemView.findViewById(R.id.txtUserId)
private val txtTitle: TextView = itemView.findViewById(R.id.txtTitle)
private val txtBody: TextView = itemView.findViewById(R.id.txtBody)
fun bind(post: Post) {
txtId.text = "ID: ${post.id}"
txtUserId.text = "User: ${post.userId}"
txtTitle.text = post.title
txtBody.text = post.body
itemView.setOnClickListener { onItemClick(post) }
}
}
}🟩 12. MainActivity
📄 MainActivity.kt
package com.example.retrofitserializationapp
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: PostAdapter
private lateinit var progressBar: ProgressBar
private lateinit var txtInfo: TextView
private lateinit var btnGetPosts: Button
private lateinit var btnGetUsers: Button
private lateinit var btnCreate: Button
private val api = RetrofitClient.api
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initializeViews()
setupRecyclerView()
setupListeners()
}
private fun initializeViews() {
recyclerView = findViewById(R.id.recyclerView)
progressBar = findViewById(R.id.progressBar)
txtInfo = findViewById(R.id.txtInfo)
btnGetPosts = findViewById(R.id.btnGetPosts)
btnGetUsers = findViewById(R.id.btnGetUsers)
btnCreate = findViewById(R.id.btnCreate)
}
private fun setupRecyclerView() {
adapter = PostAdapter { post ->
Toast.makeText(this, "Post: ${post.title}", Toast.LENGTH_SHORT).show()
}
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
}
private fun setupListeners() {
btnGetPosts.setOnClickListener { getPosts() }
btnGetUsers.setOnClickListener { getUsers() }
btnCreate.setOnClickListener { createPost() }
}
private fun getPosts() {
lifecycleScope.launch {
try {
showProgress(true)
txtInfo.text = "Obteniendo posts..."
val response = api.getPosts()
showProgress(false)
if (response.isSuccessful) {
val posts = response.body() ?: emptyList()
adapter.updatePosts(posts)
txtInfo.text = "✓ ${posts.size} posts cargados"
Toast.makeText(this@MainActivity,
"Posts obtenidos: ${posts.size}",
Toast.LENGTH_SHORT).show()
} else {
txtInfo.text = "Error: ${response.code()}"
Toast.makeText(this@MainActivity,
"Error: ${response.code()}",
Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
showProgress(false)
txtInfo.text = "Error: ${e.message}"
Toast.makeText(this@MainActivity,
"Error: ${e.message}",
Toast.LENGTH_LONG).show()
}
}
}
private fun getUsers() {
lifecycleScope.launch {
try {
showProgress(true)
txtInfo.text = "Obteniendo usuarios..."
val response = api.getUsers()
showProgress(false)
if (response.isSuccessful) {
val users = response.body() ?: emptyList()
val userInfo = buildString {
append("✓ Usuarios obtenidos: ${users.size}\n\n")
users.take(5).forEach { user ->
append("• ${user.name} (@${user.username})\n")
append(" ${user.email}\n\n")
}
}
txtInfo.text = userInfo
Toast.makeText(this@MainActivity,
"Usuarios: ${users.size}",
Toast.LENGTH_SHORT).show()
} else {
txtInfo.text = "Error: ${response.code()}"
}
} catch (e: Exception) {
showProgress(false)
txtInfo.text = "Error: ${e.message}"
Toast.makeText(this@MainActivity,
"Error: ${e.message}",
Toast.LENGTH_LONG).show()
}
}
}
private fun createPost() {
lifecycleScope.launch {
try {
showProgress(true)
txtInfo.text = "Creando post..."
val newPost = Post(
userId = 1,
title = "Post con Kotlinx Serialization",
body = "Creado usando Retrofit y Kotlinx Serialization"
)
val response = api.createPost(newPost)
showProgress(false)
if (response.isSuccessful) {
val createdPost = response.body()
txtInfo.text = "✓ Post creado con ID: ${createdPost?.id}"
Toast.makeText(this@MainActivity,
"Post creado: ${createdPost?.id}",
Toast.LENGTH_SHORT).show()
} else {
txtInfo.text = "Error: ${response.code()}"
}
} catch (e: Exception) {
showProgress(false)
txtInfo.text = "Error: ${e.message}"
Toast.makeText(this@MainActivity,
"Error: ${e.message}",
Toast.LENGTH_LONG).show()
}
}
}
private fun showProgress(show: Boolean) {
progressBar.visibility = if (show) View.VISIBLE else View.GONE
}
}🟩 13. Configuración Json
val json = Json {
ignoreUnknownKeys = true // Ignora campos JSON no mapeados
coerceInputValues = true // Convierte null en valores default
isLenient = true // Permite JSON no estricto
prettyPrint = true // JSON formateado (debug)
encodeDefaults = true // Incluye valores default
}🟩 14. Anotaciones Útiles
@Serializable
data class Example(
// Nombre personalizado
@SerialName("user_id")
val userId: Int,
// Campo opcional
val phone: String? = null,
// Valor por defecto
val active: Boolean = true,
// Transient (no serializar)
@Transient
val tempData: String = ""
)🟩 15. Comparación Final
| Aspecto | Kotlinx Serialization | Gson |
|---|---|---|
| Rendimiento | ⚡⚡⚡ | ⚡⚡ |
| Type-safety | ✓ | ✗ |
| Multiplatform | ✓ | ✗ |
| Tamaño APK | Menor | Mayor |
| Curva aprendizaje | Media | Baja |
▶️ Cómo Ejecutar
- Abre Android Studio
- Agrega el plugin de serialization
- Copia el código
- Sync Gradle
- Ejecuta y prueba
🧪 Resultado Final
App que consume API REST con Kotlinx Serialization, más rápida y type-safe que Gson.
📥 Descargar Proyecto
👉
🙌 Gracias por Visitar mi Blog
✔️ Compártelo
✔️ Déjame un comentario
✔️ SÃgueme para más contenido
❓ Preguntas Frecuentes
1. ¿Es mejor que Gson?
SÃ, Kotlinx Serialization es más rápida, type-safe y optimizada para Kotlin.
2. ¿Funciona con Java?
No, está diseñada especÃficamente para Kotlin.
3. ¿Necesito plugin especial?
SÃ, el plugin kotlin-serialization es obligatorio.
4. ¿Puedo migrar desde Gson?
SÃ, cambia @SerializedName por @SerialName y agrega @Serializable.

No hay comentarios:
Publicar un comentario