Hola amigos 馃憢 Bienvenidos a un nuevo tutorial de Universo Android. Hoy aprenderemos a usar Room, la biblioteca de persistencia de datos moderna sobre SQLite que simplifica el acceso a bases de datos locales con type-safety y menos c贸digo boilerplate.
Al finalizar este tutorial tendr谩s:
- Base de datos Room completa
- Entities, DAOs y Database
- Operaciones CRUD (Create, Read, Update, Delete)
- Queries personalizadas
- LiveData y observables
- RecyclerView con datos locales
- Migraciones de base de datos
馃煩 1. ¿Qu茅 es Room?
Room es una capa de abstracci贸n sobre SQLite que proporciona acceso fluido a la base de datos, validaci贸n en compile-time de queries SQL y observables autom谩ticos para actualizar la UI.
馃煩 2. Componentes de Room
- Entity: Representa una tabla en la BD
- DAO: Data Access Object - Define operaciones
- Database: Contiene la BD y sirve como punto de acceso
馃煩 3. Crear el Proyecto
Creamos un nuevo proyecto en Android Studio con Empty Activity.
馃煩 4. Agregar Dependencias
馃搫 build.gradle (Module: app)
plugins {
id 'com.android.application'
}
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'
// Room
implementation 'androidx.room:room-runtime:2.6.1'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
// Room para coroutines (opcional pero recomendado)
implementation 'androidx.room:room-ktx:2.6.1'
// Lifecycle components
implementation 'androidx.lifecycle:lifecycle-livedata:2.7.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.7.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
}馃煩 5. Entity (Modelo)
馃搫 Task.java
package com.example.roomapp;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "tasks_table")
public class Task {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "task_title")
private String title;
@ColumnInfo(name = "task_description")
private String description;
@ColumnInfo(name = "task_priority")
private int priority; // 1=Baja, 2=Media, 3=Alta
@ColumnInfo(name = "task_completed")
private boolean completed;
@ColumnInfo(name = "task_date")
private long date;
// Constructor
public Task(String title, String description, int priority, boolean completed, long date) {
this.title = title;
this.description = description;
this.priority = priority;
this.completed = completed;
this.date = date;
}
// Getters y Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getPriority() { return priority; }
public void setPriority(int priority) { this.priority = priority; }
public boolean isCompleted() { return completed; }
public void setCompleted(boolean completed) { this.completed = completed; }
public long getDate() { return date; }
public void setDate(long date) { this.date = date; }
}馃煩 6. DAO (Data Access Object)
馃搫 TaskDao.java
package com.example.roomapp;
import androidx.lifecycle.LiveData;
import androidx.room.*;
import java.util.List;
@Dao
public interface TaskDao {
// Insertar
@Insert
void insert(Task task);
@Insert
void insertAll(Task... tasks);
// Actualizar
@Update
void update(Task task);
// Eliminar
@Delete
void delete(Task task);
@Query("DELETE FROM tasks_table")
void deleteAllTasks();
@Query("DELETE FROM tasks_table WHERE id = :taskId")
void deleteById(int taskId);
// Consultas
@Query("SELECT * FROM tasks_table ORDER BY task_priority DESC, task_date DESC")
LiveData<List<Task>> getAllTasks();
@Query("SELECT * FROM tasks_table WHERE id = :id")
LiveData<Task> getTaskById(int id);
@Query("SELECT * FROM tasks_table WHERE task_completed = 0 ORDER BY task_priority DESC")
LiveData<List<Task>> getPendingTasks();
@Query("SELECT * FROM tasks_table WHERE task_completed = 1 ORDER BY task_date DESC")
LiveData<List<Task>> getCompletedTasks();
@Query("SELECT * FROM tasks_table WHERE task_priority = :priority ORDER BY task_date DESC")
LiveData<List<Task>> getTasksByPriority(int priority);
@Query("SELECT * FROM tasks_table WHERE task_title LIKE '%' || :searchQuery || '%' OR task_description LIKE '%' || :searchQuery || '%'")
LiveData<List<Task>> searchTasks(String searchQuery);
@Query("SELECT COUNT(*) FROM tasks_table")
LiveData<Integer> getTasksCount();
@Query("SELECT COUNT(*) FROM tasks_table WHERE task_completed = 0")
LiveData<Integer> getPendingTasksCount();
}馃煩 7. Database
馃搫 TaskDatabase.java
package com.example.roomapp;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Database(entities = {Task.class}, version = 1, exportSchema = false)
public abstract class TaskDatabase extends RoomDatabase {
public abstract TaskDao taskDao();
private static volatile TaskDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
static TaskDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (TaskDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
TaskDatabase.class,
"task_database"
)
.fallbackToDestructiveMigration()
.build();
}
}
}
return INSTANCE;
}
}馃煩 8. Repository
馃搫 TaskRepository.java
package com.example.roomapp;
import android.app.Application;
import androidx.lifecycle.LiveData;
import java.util.List;
public class TaskRepository {
private TaskDao taskDao;
private LiveData<List<Task>> allTasks;
private LiveData<List<Task>> pendingTasks;
private LiveData<Integer> tasksCount;
public TaskRepository(Application application) {
TaskDatabase database = TaskDatabase.getDatabase(application);
taskDao = database.taskDao();
allTasks = taskDao.getAllTasks();
pendingTasks = taskDao.getPendingTasks();
tasksCount = taskDao.getTasksCount();
}
// Observables
public LiveData<List<Task>> getAllTasks() {
return allTasks;
}
public LiveData<List<Task>> getPendingTasks() {
return pendingTasks;
}
public LiveData<List<Task>> getCompletedTasks() {
return taskDao.getCompletedTasks();
}
public LiveData<Integer> getTasksCount() {
return tasksCount;
}
public LiveData<List<Task>> searchTasks(String query) {
return taskDao.searchTasks(query);
}
// Operaciones en background
public void insert(Task task) {
TaskDatabase.databaseWriteExecutor.execute(() -> {
taskDao.insert(task);
});
}
public void update(Task task) {
TaskDatabase.databaseWriteExecutor.execute(() -> {
taskDao.update(task);
});
}
public void delete(Task task) {
TaskDatabase.databaseWriteExecutor.execute(() -> {
taskDao.delete(task);
});
}
public void deleteAllTasks() {
TaskDatabase.databaseWriteExecutor.execute(() -> {
taskDao.deleteAllTasks();
});
}
}馃煩 9. ViewModel
馃搫 TaskViewModel.java
package com.example.roomapp;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;
public class TaskViewModel extends AndroidViewModel {
private TaskRepository repository;
private LiveData<List<Task>> allTasks;
private LiveData<List<Task>> pendingTasks;
private LiveData<Integer> tasksCount;
public TaskViewModel(@NonNull Application application) {
super(application);
repository = new TaskRepository(application);
allTasks = repository.getAllTasks();
pendingTasks = repository.getPendingTasks();
tasksCount = repository.getTasksCount();
}
// Getters
public LiveData<List<Task>> getAllTasks() {
return allTasks;
}
public LiveData<List<Task>> getPendingTasks() {
return pendingTasks;
}
public LiveData<List<Task>> getCompletedTasks() {
return repository.getCompletedTasks();
}
public LiveData<Integer> getTasksCount() {
return tasksCount;
}
public LiveData<List<Task>> searchTasks(String query) {
return repository.searchTasks(query);
}
// Operaciones
public void insert(Task task) {
repository.insert(task);
}
public void update(Task task) {
repository.update(task);
}
public void delete(Task task) {
repository.delete(task);
}
public void deleteAllTasks() {
repository.deleteAllTasks();
}
}馃煩 10. Dise帽o XML
馃搫 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="#2196F3">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Mis Tareas"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#FFFFFF" />
<TextView
android:id="@+id/txtTaskCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/badge_background"
android:padding="8dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<Button
android:id="@+id/btnShowAll"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Todas"
android:layout_margin="4dp" />
<Button
android:id="@+id/btnShowPending"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Pendientes"
android:layout_margin="4dp" />
<Button
android:id="@+id/btnShowCompleted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Completadas"
android:layout_margin="4dp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@android:drawable/ic_input_add"
app:tint="#FFFFFF" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>馃煩 11. Layout del Item
馃搫 res/layout/item_task.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="horizontal"
android:padding="16dp">
<CheckBox
android:id="@+id/checkboxCompleted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="12dp">
<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" />
<TextView
android:id="@+id/txtDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Descripci贸n"
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginTop="4dp"
android:maxLines="2"
android:ellipsize="end" />
<TextView
android:id="@+id/txtDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fecha"
android:textSize="12sp"
android:textColor="#999999"
android:layout_marginTop="8dp" />
</LinearLayout>
<View
android:id="@+id/viewPriority"
android:layout_width="4dp"
android:layout_height="match_parent"
android:background="#4CAF50"
android:layout_marginStart="12dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>馃煩 12. Adaptador
馃搫 TaskAdapter.java
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.ViewHolder> {
private List<Task> tasks = new ArrayList<>();
private OnTaskListener listener;
public interface OnTaskListener {
void onTaskClick(Task task);
void onTaskLongClick(Task task);
void onTaskCompleteChange(Task task, boolean isCompleted);
}
public TaskAdapter(OnTaskListener listener) {
this.listener = listener;
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
notifyDataSetChanged();
}
public Task getTaskAt(int position) {
return tasks.get(position);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_task, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.bind(tasks.get(position));
}
@Override
public int getItemCount() {
return tasks.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
CheckBox checkboxCompleted;
TextView txtTitle, txtDescription, txtDate;
View viewPriority;
ViewHolder(View itemView) {
super(itemView);
checkboxCompleted = itemView.findViewById(R.id.checkboxCompleted);
txtTitle = itemView.findViewById(R.id.txtTitle);
txtDescription = itemView.findViewById(R.id.txtDescription);
txtDate = itemView.findViewById(R.id.txtDate);
viewPriority = itemView.findViewById(R.id.viewPriority);
}
void bind(Task task) {
txtTitle.setText(task.getTitle());
txtDescription.setText(task.getDescription());
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault());
txtDate.setText(sdf.format(new Date(task.getDate())));
checkboxCompleted.setChecked(task.isCompleted());
// Estilo tachado si est谩 completada
if (task.isCompleted()) {
txtTitle.setPaintFlags(txtTitle.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
txtDescription.setPaintFlags(txtDescription.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
txtTitle.setPaintFlags(txtTitle.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
txtDescription.setPaintFlags(txtDescription.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
}
// Color seg煤n prioridad
int priorityColor;
switch (task.getPriority()) {
case 3: // Alta
priorityColor = 0xFFF44336;
break;
case 2: // Media
priorityColor = 0xFFFF9800;
break;
default: // Baja
priorityColor = 0xFF4CAF50;
break;
}
viewPriority.setBackgroundColor(priorityColor);
// Listeners
checkboxCompleted.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (listener != null) {
listener.onTaskCompleteChange(task, isChecked);
}
});
itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onTaskClick(task);
}
});
itemView.setOnLongClickListener(v -> {
if (listener != null) {
listener.onTaskLongClick(task);
}
return true;
});
}
}
}馃煩 13. MainActivity
馃搫 MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
public class MainActivity extends AppCompatActivity {
private TaskViewModel taskViewModel;
private TaskAdapter adapter;
private TextView txtTaskCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
FloatingActionButton fabAdd = findViewById(R.id.fabAdd);
Button btnShowAll = findViewById(R.id.btnShowAll);
Button btnShowPending = findViewById(R.id.btnShowPending);
Button btnShowCompleted = findViewById(R.id.btnShowCompleted);
txtTaskCount = findViewById(R.id.txtTaskCount);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new TaskAdapter(new TaskAdapter.OnTaskListener() {
@Override
public void onTaskClick(Task task) {
Toast.makeText(MainActivity.this, task.getTitle(), Toast.LENGTH_SHORT).show();
}
@Override
public void onTaskLongClick(Task task) {
taskViewModel.delete(task);
Toast.makeText(MainActivity.this, "Tarea eliminada", Toast.LENGTH_SHORT).show();
}
@Override
public void onTaskCompleteChange(Task task, boolean isCompleted) {
task.setCompleted(isCompleted);
taskViewModel.update(task);
}
});
recyclerView.setAdapter(adapter);
// Swipe to delete
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
Task task = adapter.getTaskAt(viewHolder.getAdapterPosition());
taskViewModel.delete(task);
Toast.makeText(MainActivity.this, "Tarea eliminada", Toast.LENGTH_SHORT).show();
}
}).attachToRecyclerView(recyclerView);
taskViewModel = new ViewModelProvider(this).get(TaskViewModel.class);
taskViewModel.getAllTasks().observe(this, tasks -> {
adapter.setTasks(tasks);
});
taskViewModel.getTasksCount().observe(this, count -> {
txtTaskCount.setText(String.valueOf(count != null ? count : 0));
});
fabAdd.setOnClickListener(v -> addSampleTask());
btnShowAll.setOnClickListener(v -> {
taskViewModel.getAllTasks().observe(this, tasks -> adapter.setTasks(tasks));
});
btnShowPending.setOnClickListener(v -> {
taskViewModel.getPendingTasks().observe(this, tasks -> adapter.setTasks(tasks));
});
btnShowCompleted.setOnClickListener(v -> {
taskViewModel.getCompletedTasks().observe(this, tasks -> adapter.setTasks(tasks));
});
}
private void addSampleTask() {
Task task = new Task(
"Nueva Tarea",
"Descripci贸n de la tarea",
2, // Prioridad media
false,
System.currentTimeMillis()
);
taskViewModel.insert(task);
Toast.makeText(this, "Tarea agregada", Toast.LENGTH_SHORT).show();
}
}馃煩 14. Drawable Badge
馃搫 res/drawable/badge_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF5722" />
</shape>▶️ C贸mo Ejecutar
- Abre Android Studio
- Copia el c贸digo
- Sync Gradle
- Presiona Run
- Agrega, completa y elimina tareas
馃И Resultado Final
App completa con base de datos SQLite usando Room, CRUD operations, filtros y swipe to delete.
馃摜 Descargar Proyecto
馃憠
馃檶 Gracias por Visitar mi Blog
✔️ Comp谩rtelo
✔️ D茅jame un comentario
✔️ S铆gueme para m谩s contenido
❓ Preguntas Frecuentes
1. ¿Room reemplaza SQLite?
No, Room es una capa sobre SQLite que facilita su uso con menos c贸digo y m谩s seguridad.
2. ¿Necesito conocer SQL?
Es 煤til pero no obligatorio. Room genera mucho c贸digo autom谩ticamente.
3. ¿Qu茅 es LiveData?
Un observable que actualiza la UI autom谩ticamente cuando los datos cambian.
4. ¿C贸mo migro entre versiones?
Define Migration objects o usa fallbackToDestructiveMigration() para desarrollo.

No hay comentarios:
Publicar un comentario