Para guardar datos en una base de datos usando Android tenemos que recurrir al maravilloso gestor SQLite3. Pues bien, hoy veremos cómo trabajar con SQLite desde Android usando Java.
Lo que veremos será un CRUD o ABC de Android con SQLite en donde veremos un insert, update, delete y select de SQL con Android.
Al final tendremos una app móvil simple que permitirá interactuar con SQLite para realizar las operaciones básicas que se ve así:
Todo esto usando SQLite y una clase que extiende de SQLiteOpenHelper.
Al final tendremos múltiples actividades para interactuar con SQLite. La actividad principal tendrá una lista con RecyclerView y un botón FAB que abre la actividad para agregar.
Cuando se toque un elemento de la lista se mandará a una nueva activity para editar y finalmente en el toque largo se mostrará una alerta para eliminar.
Lo que vamos a gestionar es una base de datos de Mascotas, guardaremos el nombre y la edad. Será simple y a partir de ello podríamos extender los campos.
Esta app es totalmente gratuita y open source, su repositorio lo encuentras aquí. Si quieres descargar la app visita la página releases.
Una demostración del proyecto puede ser vista en YouTube (ya que estamos en esto te invito a suscribirte):
Nota: a través del post iré mostrando fragmentos de código importantes, pero si quieres ver el código completo y actualizado míralo en GitHub.
Ahora sí comencemos.
No vamos a hacer relaciones porque no son necesarias, tampoco agregaremos índices. Simplemente crearemos un campo autoincrementable y otros dos campos para el nombre y la edad.
Por lo tanto la creación de la tabla queda así:
CREATE TABLE IF NOT EXISTS mascotas(
id integer primary key autoincrement,
nombre text,
edad int
);
La base de datos de SQLite igualmente se llamará mascotas. En resumen, tanto la base de datos como la tabla tendrán el mismo nombre.
Comenzamos definiendo una clase que extiende de un Helper, mejor dicho, SQLiteOpenHelper. Hay que sobrescribir algunas funciones de la interfaz, las cuales son:
Cada cosa tiene su forma. Comenzando con el constructor que es en donde se recibe el contexto y se llama al método padre con la versión de la base de datos.
El método onCreate
es llamado cuando la base de datos ha sido creada, es llamado con un objeto de tipo SQLiteDatabase
que es la base de datos “física” que ha sido creada.
Por otro lado, el método onUpgrade
es cuando actualizamos la estructura o algo de la base de datos pero nuestra app ya está instalada, entonces en este momento comparamos versiones y ajustamos para aquellos usuarios que tengan otra versión. Esto de onUpgrade lo veremos en otra ocasión.
Aquí tenemos el código del ayudante que nos va a permitir interactuar con la base de datos de SQLite:
package me.parzibyte.crudsqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class AyudanteBaseDeDatos extends SQLiteOpenHelper {
private static final String NOMBRE_BASE_DE_DATOS = "mascotas",
NOMBRE_TABLA_MASCOTAS = "mascotas";
private static final int VERSION_BASE_DE_DATOS = 1;
public AyudanteBaseDeDatos(Context context) {
super(context, NOMBRE_BASE_DE_DATOS, null, VERSION_BASE_DE_DATOS);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(String.format("CREATE TABLE IF NOT EXISTS %s(id integer primary key autoincrement, nombre text, edad int)", NOMBRE_TABLA_MASCOTAS));
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Además de sobrescribir los métodos estamos definiendo unas constantes que nos van a ayudar a escribir mejor código.
Una parte importante es dentro del método onCreate
, pues ahí creamos la tabla. Si hubiera más tablas las crearíamos ahí con db.execSQL
.
Algunos pensarán que como la base de datos permanece local, no hay problema de que haya inyecciones SQL porque el usuario se haría daño a sí mismo; sin embargo no vamos a confiarnos de ello y vamos a escapar las consultas para asegurar nuestra app.
No vamos a quebrarnos mucho la cabeza, pues los métodos que ofrece la API están enfocados a prevenir inyecciones SQL y a usar algo como sentencias preparadas. Es decir, este tutorial es totalmente seguro en cuanto a las inyecciones SQL.
Esto no es MVC ni esas cosas que están de moda; pero he tratado de separar los componentes para que al final sea una programación modular y sencilla.
He puesto un controlador de mascotas, que no hace más que “pegar” la vista con el ayudante de la base de datos; así podemos tener múltiples métodos que gestionen nuestras mascotas, los cuales podrían ser reutilizados.
El controlador se encarga de gestionar todas las operaciones de la base de datos con la siguiente relación:
Como te habrás fijado, todo esto depende de la clase Mascota, que es la abstracción de una mascota del mundo real. Veámoslo:
package me.parzibyte.crudsqlite.modelos;
public class Mascota {
private String nombre;
private int edad;
private long id; // El ID de la BD
public Mascota(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
// Constructor para cuando instanciamos desde la BD
public Mascota(String nombre, int edad, long id) {
this.nombre = nombre;
this.edad = edad;
this.id = id;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
@Override
public String toString() {
return "Mascota{" +
"nombre='" + nombre + '\'' +
", edad=" + edad +
'}';
}
}
Veamos operación por operación.
El código queda así:
public long nuevaMascota(Mascota mascota) {
// writable porque vamos a insertar
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues valoresParaInsertar = new ContentValues();
valoresParaInsertar.put("nombre", mascota.getNombre());
valoresParaInsertar.put("edad", mascota.getEdad());
return baseDeDatos.insert(NOMBRE_TABLA, null, valoresParaInsertar);
}
Comenzamos obteniendo la base de datos a la que se puede escribir, es decir, la writable. Preparamos un objeto de ContentValues que llevará el nombre del campo de la base de datos y el valor que se va a insertar.
Es importante que pongamos el mismo nombre de la columna de la tabla dentro del ContentValues. Como vemos, tomamos los datos del objeto mascota y sus métodos get.
Llamamos al método put
por cada valor que vamos a insertar, y después de ello llamamos al método insert
de la base de datos con estos argumentos:
ContentValues
.Todo esto regresa un dato de tipo long que regresamos igualmente, el cual representa el id del valor insertado o -1 en caso de que algo haya fallado.
Para hacer un select llamamos al método query
de la base de datos. Por cierto, ahora solamente necesitamos la base de datos en modo lectura, no escritura.
Al inicio declaramos una lista de mascotas en un ArrayList
de tipo Mascota
; estamos reutilizando la clase.
public ArrayList<Mascota> obtenerMascotas() {
ArrayList<Mascota> mascotas = new ArrayList<>();
// readable porque no vamos a modificar, solamente leer
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getReadableDatabase();
// SELECT nombre, edad, id
String[] columnasAConsultar = {"nombre", "edad", "id"};
Cursor cursor = baseDeDatos.query(
NOMBRE_TABLA,//from mascotas
columnasAConsultar,
null,
null,
null,
null,
null
);
if (cursor == null) {
/*
Salimos aquí porque hubo un error, regresar
lista vacía
*/ return mascotas;
}
// Si no hay datos, igualmente regresamos la lista vacía
if (!cursor.moveToFirst()) return mascotas;
// En caso de que sí haya, iteramos y vamos agregando los
// datos a la lista de mascotas
do {
// El 0 es el número de la columna, como seleccionamos
// nombre, edad,id entonces el nombre es 0, edad 1 e id es 2
String nombreObtenidoDeBD = cursor.getString(0);
int edadObtenidaDeBD = cursor.getInt(1);
long idMascota = cursor.getLong(2);
Mascota mascotaObtenidaDeBD = new Mascota(nombreObtenidoDeBD, edadObtenidaDeBD, idMascota);
mascotas.add(mascotaObtenidaDeBD);
} while (cursor.moveToNext());
// Fin del ciclo. Cerramos cursor y regresamos la lista de mascotas :)
cursor.close();
return mascotas;
}
Este método de query
es realmente complejo a primera vista, pero déjame explicarte el significado de los argumentos. Por cierto, si no queremos mandar un argumento lo dejamos en null
.
String
únicamente con el valor edad = ?; para hacer la comparación veamos el siguiente argumento.Como estamos recuperando todas las mascotas, solamente nos importan los 2 primeros argumentos.
Este método de query
regresa un cursor que vamos a iterar. Para empezar, si regresa nulo regresamos la lista vacía.
Luego comprobamos si tiene datos con moveToFirst
y en caso de que no tenga, también regresamos la lista vacía.
Finalmente, en caso de que sí tenga datos, lo recorremos con un ciclo while
que seguirá mientras el cursor se mueva hacia adelante, es decir, que el método moveToNext
regrese true
.
Dentro del ciclo obtenemos el valor de la base de datos; podemos llamar a cursor.getTipoDeDato
por ejemplo cursor.getString
o cursor.getIn
t; le pasamos como argumento el índice de la columna. Aquí importa el orden de las columnas que mandamos en el segundo argumento de query
.
Luego creamos un objeto de tipo Mascota
y la agregamos con add
a nuestra lista
Al final de la función regresamos la lista de mascotas.
Este método es un poco más complicado que insertar pero menos difícil que obtener. Utilizamos de nuevo los ContentValues
para mandar los nuevos datos, pero para hacer el where
necesitamos dos cosas:
where
, por ejemplo, "where id = ?"
En este caso es solamente un valor pero podrían ser muchos, es por eso que se mandan en un arreglo de tipo String
. Queda así:
public int guardarCambios(Mascota mascotaEditada) {
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues valoresParaActualizar = new ContentValues();
valoresParaActualizar.put("nombre", mascotaEditada.getNombre());
valoresParaActualizar.put("edad", mascotaEditada.getEdad());
// where id...
String campoParaActualizar = "id = ?";
// ... = idMascota
String[] argumentosParaActualizar = {String.valueOf(mascotaEditada.getId())};
return baseDeDatos.update(NOMBRE_TABLA, valoresParaActualizar, campoParaActualizar, argumentosParaActualizar);
}
Por cierto, regresamos lo mismo que regresa el método update
, lo cual es un entero que indica el número de columnas que fueron afectadas al realizar la transacción. Teóricamente en este caso siempre sería 1, porque como actualizamos por id y el id es único, solamente se actualizaría una columna.
Sin embargo, si actualizáramos usando al nombre y hubiera nombres repetidos entonces se podrían actualizar más columnas. Casi lo olvido, si mandamos los 2 últimos argumentos en null
, no se hace filtro y se modifica toda la tabla.
Veamos el último método que igualmente recibe un objeto de tipo Mascota
y elimina de la base de datos.
public int eliminarMascota(Mascota mascota) {
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
String[] argumentos = {String.valueOf(mascota.getId())};
return baseDeDatos.delete(NOMBRE_TABLA, "id = ?", argumentos);
}
Es muy parecido al de actualizar. Primero recibe el nombre de la tabla, luego el where que se hará para eliminar y finalmente los argumentos que se le pasan al where.
Poniendo todo junto, en controlador queda de la siguiente manera:
package me.parzibyte.crudsqlite.controllers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
import me.parzibyte.crudsqlite.AyudanteBaseDeDatos;
import me.parzibyte.crudsqlite.modelos.Mascota;
public class MascotasController {
private AyudanteBaseDeDatos ayudanteBaseDeDatos;
private String NOMBRE_TABLA = "mascotas";
public MascotasController(Context contexto) {
ayudanteBaseDeDatos = new AyudanteBaseDeDatos(contexto);
}
public int eliminarMascota(Mascota mascota) {
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
String[] argumentos = {String.valueOf(mascota.getId())};
return baseDeDatos.delete(NOMBRE_TABLA, "id = ?", argumentos);
}
public long nuevaMascota(Mascota mascota) {
// writable porque vamos a insertar
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues valoresParaInsertar = new ContentValues();
valoresParaInsertar.put("nombre", mascota.getNombre());
valoresParaInsertar.put("edad", mascota.getEdad());
return baseDeDatos.insert(NOMBRE_TABLA, null, valoresParaInsertar);
}
public int guardarCambios(Mascota mascotaEditada) {
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getWritableDatabase();
ContentValues valoresParaActualizar = new ContentValues();
valoresParaActualizar.put("nombre", mascotaEditada.getNombre());
valoresParaActualizar.put("edad", mascotaEditada.getEdad());
// where id...
String campoParaActualizar = "id = ?";
// ... = idMascota
String[] argumentosParaActualizar = {String.valueOf(mascotaEditada.getId())};
return baseDeDatos.update(NOMBRE_TABLA, valoresParaActualizar, campoParaActualizar, argumentosParaActualizar);
}
public ArrayList<Mascota> obtenerMascotas() {
ArrayList<Mascota> mascotas = new ArrayList<>();
// readable porque no vamos a modificar, solamente leer
SQLiteDatabase baseDeDatos = ayudanteBaseDeDatos.getReadableDatabase();
// SELECT nombre, edad, id
String[] columnasAConsultar = {"nombre", "edad", "id"};
Cursor cursor = baseDeDatos.query(
NOMBRE_TABLA,//from mascotas
columnasAConsultar,
null,
null,
null,
null,
null
);
if (cursor == null) {
/*
Salimos aquí porque hubo un error, regresar
lista vacía
*/ return mascotas;
}
// Si no hay datos, igualmente regresamos la lista vacía
if (!cursor.moveToFirst()) return mascotas;
// En caso de que sí haya, iteramos y vamos agregando los
// datos a la lista de mascotas
do {
// El 0 es el número de la columna, como seleccionamos
// nombre, edad,id entonces el nombre es 0, edad 1 e id es 2
String nombreObtenidoDeBD = cursor.getString(0);
int edadObtenidaDeBD = cursor.getInt(1);
long idMascota = cursor.getLong(2);
Mascota mascotaObtenidaDeBD = new Mascota(nombreObtenidoDeBD, edadObtenidaDeBD, idMascota);
mascotas.add(mascotaObtenidaDeBD);
} while (cursor.moveToNext());
// Fin del ciclo. Cerramos cursor y regresamos la lista de mascotas :)
cursor.close();
return mascotas;
}
}
Para usarlo, se crea una instancia del mismo y se le pasa el contexto para que a su vez construya la base de datos con ese contexto.
Ahora veamos cómo es que el controlador de las mascotas interactúa directamente con la vista, es decir, lo que ve el usuario. Comencemos con la actividad principal que tiene una lista (un RecyclerView) cuyos elementos son un layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="5dp">
<TextView
android:id="@+id/tvNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:text="Aquí el nombre de la mascota"
android:textColor="@color/colorPrimaryDark"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvEdad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:text="Aquí su edad"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvNombre" />
</android.support.constraint.ConstraintLayout>
Eso será repetido por el RecyclerView dentro de la actividad principal, cuyo diseño queda así:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ScrollView 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"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerViewMascotas"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</android.support.constraint.ConstraintLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabAgregarMascota"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="normal"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_add_white_24dp" />
</android.support.design.widget.CoordinatorLayout>
En esa actividad ponemos un FAB y la lista. Todo va así:
Ahora veamos el código de la actividad principal:
package me.parzibyte.crudsqlite;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import me.parzibyte.crudsqlite.controllers.MascotasController;
import me.parzibyte.crudsqlite.modelos.Mascota;
public class MainActivity extends AppCompatActivity {
private List<Mascota> listaDeMascotas;
private RecyclerView recyclerView;
private AdaptadorMascotas adaptadorMascotas;
private MascotasController mascotasController;
private FloatingActionButton fabAgregarMascota;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Ojo: este código es generado automáticamente, pone la vista y ya, pero
// no tiene nada que ver con el código que vamos a escribir
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Lo siguiente sí es nuestro ;)
// Definir nuestro controlador
mascotasController = new MascotasController(MainActivity.this);
// Instanciar vistas
recyclerView = findViewById(R.id.recyclerViewMascotas);
fabAgregarMascota = findViewById(R.id.fabAgregarMascota);
// Por defecto es una lista vacía,
// se la ponemos al adaptador y configuramos el recyclerView
listaDeMascotas = new ArrayList<>();
adaptadorMascotas = new AdaptadorMascotas(listaDeMascotas);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adaptadorMascotas);
// Una vez que ya configuramos el RecyclerView le ponemos los datos de la BD
refrescarListaDeMascotas();
// Listener de los clicks en la lista, o sea el RecyclerView
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getApplicationContext(), recyclerView, new RecyclerTouchListener.ClickListener() {
@Override // Un toque sencillo
public void onClick(View view, int position) {
// Pasar a la actividad EditarMascotaActivity.java
Mascota mascotaSeleccionada = listaDeMascotas.get(position);
Intent intent = new Intent(MainActivity.this, EditarMascotaActivity.class);
intent.putExtra("idMascota", mascotaSeleccionada.getId());
intent.putExtra("nombreMascota", mascotaSeleccionada.getNombre());
intent.putExtra("edadMascota", mascotaSeleccionada.getEdad());
startActivity(intent);
}
@Override // Un toque largo
public void onLongClick(View view, int position) {
final Mascota mascotaParaEliminar = listaDeMascotas.get(position);
AlertDialog dialog = new AlertDialog
.Builder(MainActivity.this)
.setPositiveButton("Sí, eliminar", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mascotasController.eliminarMascota(mascotaParaEliminar);
refrescarListaDeMascotas();
}
})
.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setTitle("Confirmar")
.setMessage("¿Eliminar a la mascota " + mascotaParaEliminar.getNombre() + "?")
.create();
dialog.show();
}
}));
// Listener del FAB
fabAgregarMascota.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Simplemente cambiamos de actividad
Intent intent = new Intent(MainActivity.this, AgregarMascotaActivity.class);
startActivity(intent);
}
});
// Créditos
fabAgregarMascota.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("Acerca de")
.setMessage("CRUD de Android con SQLite creado por parzibyte [parzibyte.me]\n\nIcons made by Freepik from www.flaticon.com ")
.setNegativeButton("Cerrar", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogo, int which) {
dialogo.dismiss();
}
})
.setPositiveButton("Sitio web", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intentNavegador = new Intent(Intent.ACTION_VIEW, Uri.parse("https://parzibyte.me"));
startActivity(intentNavegador);
}
})
.create()
.show();
return false;
}
});
}
@Override
protected void onResume() {
super.onResume();
refrescarListaDeMascotas();
}
public void refrescarListaDeMascotas() {
/*
* ==========
* Justo aquí obtenemos la lista de la BD
* y se la ponemos al RecyclerView
* ============
*
* */ if (adaptadorMascotas == null) return;
listaDeMascotas = mascotasController.obtenerMascotas();
adaptadorMascotas.setListaDeMascotas(listaDeMascotas);
adaptadorMascotas.notifyDataSetChanged();
}
}
Lo importante aquí es el método refrescarListaDeMascotas
, el cual llama a nuestro controlador de mascotas y las dibuja. Por cierto, el controlador está siendo instanciado más arriba.
Más abajo se definen escuchadores. Por ejemplo, se escucha cuando se toca una fila (eso está dentro del adaptador que es algo más avanzado, así como el listener de la lista) o cuando se mantiene presionado un elemento.
Cuando se hace click en una fila, se va a la actividad de editar. Cuando se mantiene pulsada, sale una alerta que pide una confirmación.
Finalmente al presionar el FAB (el botón de la esquina) se va a la actividad de crear.
Cuando se mantiene presionado el elemento se ejecuta este código:
@Override // Un toque largo
public void onLongClick(View view, int position) {
final Mascota mascotaParaEliminar = listaDeMascotas.get(position);
AlertDialog dialog = new AlertDialog
.Builder(MainActivity.this)
.setPositiveButton("Sí, eliminar", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mascotasController.eliminarMascota(mascotaParaEliminar);
refrescarListaDeMascotas();
}
})
.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setTitle("Confirmar")
.setMessage("¿Eliminar a la mascota " + mascotaParaEliminar.getNombre() + "?")
.create();
dialog.show();
}
Para obtener la mascota que fue tocada se usa el método get
de la lista de mascotas, se le pasa el índice que obtenemos del listener.
Creamos un diálogo y cuando confirman la operación, la mascota se elimina desde el controlador.
Cuando se toca el botón de la esquina, se dirige a una nueva actividad que tiene este diseño:
El código XML es este:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AgregarMascotaActivity">
<EditText
android:id="@+id/etNombre"
android:layout_width="278dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="Nombre de la mascota"
android:inputType="textPersonName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/etEdad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:ems="10"
android:hint="Edad"
android:inputType="number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etNombre" />
<Button
android:id="@+id/btnAgregarMascota"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:text="Guardar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etEdad" />
<Button
android:id="@+id/btnCancelarNuevaMascota"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="Cancelar"
style="@style/Widget.AppCompat.Button.Colored"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btnAgregarMascota"
app:layout_constraintTop_toBottomOf="@+id/etEdad" />
</android.support.constraint.ConstraintLayout>
Y el de Java este:
package me.parzibyte.crudsqlite;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import me.parzibyte.crudsqlite.controllers.MascotasController;
import me.parzibyte.crudsqlite.modelos.Mascota;
public class AgregarMascotaActivity extends AppCompatActivity {
private Button btnAgregarMascota, btnCancelarNuevaMascota;
private EditText etNombre, etEdad;
private MascotasController mascotasController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_agregar_mascota);
// Instanciar vistas
etNombre = findViewById(R.id.etNombre);
etEdad = findViewById(R.id.etEdad);
btnAgregarMascota = findViewById(R.id.btnAgregarMascota);
btnCancelarNuevaMascota = findViewById(R.id.btnCancelarNuevaMascota);
// Crear el controlador
mascotasController = new MascotasController(AgregarMascotaActivity.this);
// Agregar listener del botón de guardar
btnAgregarMascota.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Resetear errores a ambos
etNombre.setError(null);
etEdad.setError(null);
String nombre = etNombre.getText().toString(),
edadComoCadena = etEdad.getText().toString();
if ("".equals(nombre)) {
etNombre.setError("Escribe el nombre de la mascota");
etNombre.requestFocus();
return;
}
if ("".equals(edadComoCadena)) {
etEdad.setError("Escribe la edad de la mascota");
etEdad.requestFocus();
return;
}
// Ver si es un entero
int edad;
try {
edad = Integer.parseInt(etEdad.getText().toString());
} catch (NumberFormatException e) {
etEdad.setError("Escribe un número");
etEdad.requestFocus();
return;
}
// Ya pasó la validación
Mascota nuevaMascota = new Mascota(nombre, edad);
long id = mascotasController.nuevaMascota(nuevaMascota);
if (id == -1) {
// De alguna manera ocurrió un error
Toast.makeText(AgregarMascotaActivity.this, "Error al guardar. Intenta de nuevo", Toast.LENGTH_SHORT).show();
} else {
// Terminar
finish();
}
}
});
// El de cancelar simplemente cierra la actividad
btnCancelarNuevaMascota.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
Como vemos, se hace una validación simple y en caso de que se pase entonces se crea una nueva mascota para ser pasada como argumento al método del controlador que se encarga de hacer el insert.
En caso de que se inserte correctamente, se cierra la actividad. Y si no, se indica.
Cuando se toca una mascota se va a una actividad que es idéntica al de insertar, pero que actualiza en lugar de insertar; es decir, llama a otro método del controlador.
Por cierto, como se va a editar debemos pasarle a la actividad un dato, el cual es la mascota editada. Por ello es que cuando se toca se hace esto:
@Override // Un toque sencillo
public void onClick(View view, int position) {
// Pasar a la actividad EditarMascotaActivity.java
Mascota mascotaSeleccionada = listaDeMascotas.get(position);
Intent intent = new Intent(MainActivity.this, EditarMascotaActivity.class);
intent.putExtra("idMascota", mascotaSeleccionada.getId());
intent.putExtra("nombreMascota", mascotaSeleccionada.getNombre());
intent.putExtra("edadMascota", mascotaSeleccionada.getEdad());
startActivity(intent);
}
Del otro lado, en la actividad que edita, se tiene este código:
package me.parzibyte.crudsqlite;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import me.parzibyte.crudsqlite.controllers.MascotasController;
import me.parzibyte.crudsqlite.modelos.Mascota;
public class EditarMascotaActivity extends AppCompatActivity {
private EditText etEditarNombre, etEditarEdad;
private Button btnGuardarCambios, btnCancelarEdicion;
private Mascota mascota;//La mascota que vamos a estar editando
private MascotasController mascotasController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_editar_mascota);
// Recuperar datos que enviaron
Bundle extras = getIntent().getExtras();
// Si no hay datos (cosa rara) salimos
if (extras == null) {
finish();
return;
}
// Instanciar el controlador de las mascotas
mascotasController = new MascotasController(EditarMascotaActivity.this);
// Rearmar la mascota
// Nota: igualmente solamente podríamos mandar el id y recuperar la mascota de la BD
long idMascota = extras.getLong("idMascota");
String nombreMascota = extras.getString("nombreMascota");
int edadMascota = extras.getInt("edadMascota");
mascota = new Mascota(nombreMascota, edadMascota, idMascota);
// Ahora declaramos las vistas
etEditarEdad = findViewById(R.id.etEditarEdad);
etEditarNombre = findViewById(R.id.etEditarNombre);
btnCancelarEdicion = findViewById(R.id.btnCancelarEdicionMascota);
btnGuardarCambios = findViewById(R.id.btnGuardarCambiosMascota);
// Rellenar los EditText con los datos de la mascota
etEditarEdad.setText(String.valueOf(mascota.getEdad()));
etEditarNombre.setText(mascota.getNombre());
// Listener del click del botón para salir, simplemente cierra la actividad
btnCancelarEdicion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
// Listener del click del botón que guarda cambios
btnGuardarCambios.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Remover previos errores si existen
etEditarNombre.setError(null);
etEditarEdad.setError(null);
// Crear la mascota con los nuevos cambios pero ponerle
// el id de la anterior
String nuevoNombre = etEditarNombre.getText().toString();
String posibleNuevaEdad = etEditarEdad.getText().toString();
if (nuevoNombre.isEmpty()) {
etEditarNombre.setError("Escribe el nombre");
etEditarNombre.requestFocus();
return;
}
if (posibleNuevaEdad.isEmpty()) {
etEditarEdad.setError("Escribe la edad");
etEditarEdad.requestFocus();
return;
}
// Si no es entero, igualmente marcar error
int nuevaEdad;
try {
nuevaEdad = Integer.parseInt(posibleNuevaEdad);
} catch (NumberFormatException e) {
etEditarEdad.setError("Escribe un número");
etEditarEdad.requestFocus();
return;
}
// Si llegamos hasta aquí es porque los datos ya están validados
Mascota mascotaConNuevosCambios = new Mascota(nuevoNombre, nuevaEdad, mascota.getId());
int filasModificadas = mascotasController.guardarCambios(mascotaConNuevosCambios);
if (filasModificadas != 1) {
// De alguna forma ocurrió un error porque se debió modificar únicamente una fila
Toast.makeText(EditarMascotaActivity.this, "Error guardando cambios. Intente de nuevo.", Toast.LENGTH_SHORT).show();
} else {
// Si las cosas van bien, volvemos a la principal
// cerrando esta actividad
finish();
}
}
});
}
}
Si se actualiza correctamente, la actividad se cierra. Si no, se indica.
Cuando se cierra una actividad se vuelve a la principal, ahí escuchamos el método onResume y refrescamos la lista de mascotas.
De este modo, cuando se “regresa” de editar o crear una mascota, se vuelven a consultar. Como esto es después de haber realizado la operación, se obtiene una lista “fresca” de las mascotas.
Así es como terminamos este largo tutorial. ¿Crees que es difícil de leer? imagina qué tan complicado fue escribir el artículo y el código.
Recuerda, puedes comentar tus dudas y ver el código completo, así como descargar la app. No olvides seguirme en mis redes sociales.
Te invito a ver más sobre Android, Java o Bases de datos.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
Ver comentarios
Buenas noches estoy guiándome con tu tutorial pero en la mía guardo productos con su nombre, precio y stock, tienes alguna idea de como hacer que al registrar un producto en el edittext stock aparezca por defecto el número 0 (No me refiero a la propiedad Text del EditText), después se guarde ese registro en la BD y luego al dar click en el recyclerview lo pueda editar para cambiar el 0 por otro numero? Soy novata en esto.
Buenas noches. Claro, en este caso si necesita ayuda por favor envíeme un mensaje en https://parzibyte.me/#contacto
Hola tengo una consulta, hice algunos cambios y al seleccionar un ítem de la lista muestro el activity solamente para ver los datos porque a los edit text les puse input type en null, luego con un botón para editar me mostraría otro activity pero con los edit text rellenados y listos para editar, pero al oprimir el botón que me manda a el 3er. Activity no sucede nada, talvez al ser el 3er. Activity no puede traer de manera correcta los datos seleccionados del primero, es lo único que se me ocurre, que puedo estar haciendo mal?
Hola. Sería cuestión de revisar su proyecto para encontrar el problema. Recuerde que si necesita ayuda puede contactarme en https://parzibyte.me/#contacto
¿Y AdaptadorMascotas donde lo creas?
No veo por ningún sitio esa clase creada
Hola
Yo lo veo claramente en el repositorio que dejé casi al inicio del post en letras resaltadas: https://github.com/parzibyte/CRUD-SQLite/blob/master/app/src/main/java/me/parzibyte/crudsqlite/AdaptadorMascotas.java
También indico lo siguiente: "Nota: a través del post iré mostrando fragmentos de código importantes, pero si quieres ver el código completo y actualizado míralo en GitHub."
Le agradecería si lee las indicaciones correctamente
Saludos
Me ha servido, perfecto, el codigo esta bien por partes identificadas
hola tengo un problema ala hora de que me muestre los datos en los respectivos tvNombre , tvEdad, yo cree 5 TextView más, me edita los datos, y elimina y ademas los lista , pero soo muestra el nombre y la edad , nose en que estoy fallando , pero debo de tenerlo para mañana ,algún dato de como mostrar esos tv que añadi ?
Mira el adaptador del RecyclerView, tal vez no los estás referenciando bien. También puedes enviarme un mensaje para que yo lo resuelva por ti:
https://parzibyte.me/blog/contacto
Más sobre RecyclerView: https://parzibyte.me/blog/2019/09/26/android-recyclerview-tutorial/
Que tal muy buen día colega, me ha servido bastante tu post sobre la app solo tengo una duda, si quisiera agregar un elemento SerchView en la app, para que este solo me muestre los resultados encontrados en la lista como se lo integrarías?
Saludos!
Hola estimado me parece que ya te respondí por Facebook. Saludos :)