Blog para desarrollo de aplicaciones en Android, aprende paso a paso como crear aplicaciones.

Usamos cookies propias y de terceros que entre otras cosas recogen datos sobre sus hábitos de navegación para mostrarle publicidad personalizada y realizar análisis de uso de nuestro sitio.
Si continúa navegando consideramos que acepta su uso. OK Más información | Y más

SQLite con Room en Android

SQLite con Room en Android

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

  1. Abre Android Studio
  2. Copia el código
  3. Sync Gradle
  4. Presiona Run
  5. 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