Vous êtes sur la page 1sur 47

Abrir Android Studio y crea un proyecto llamado “Lawyers App” con la siguiente configuración:

Con esto listo, procedamos a los pasos para implementar nuestra SQLite App.

DEFINIR CONTRATO DE LA BASE DE DATOS


La forma en que una base de datos está estructurada (cantidad de tablas, registros, índices,
etc.) y el conjunto de convenciones para nombrar sus objetos se les llama Esquema. Por lo
general el esquema inicial se guarda en un Script que nos permita recuperar las condiciones
previas en cualquier momento.
Con SQLite no es diferente, por lo que debes crear un esquema predefinido para
implementarlo a la hora de crear tu base de datos.
La documentación de Android nos recomienda crear una clase llamada Contract Class, la cual
guarda como constantes todas las características de la base de datos.
Crear clase de la entidad abogado
Nuestro ejemplo está basado es un diseño compuesto de una entidad llamada Lawyer, cuyos
atributos son:
id

nombre
especialidad
número de teléfono
biografía
avatar
Para representarla crea un nuevo paquete Java con el nombre data. Dentro de este, añade una
clase llamada Lawyer.

Lawyer.java
**
* Entidad "abogado"
*/
public class Lawyer {
private String id;
private String name;
private String specialty;
private String phoneNumber;
private String bio;
private String avatarUri;

public Lawyer(String name,


String specialty, String phoneNumber,
String bio, String avatarUri) {
this.id = UUID.randomUUID().toString();
this.name = name;
this.specialty = specialty;
this.phoneNumber = phoneNumber;
this.bio = bio;
this.avatarUri = avatarUri;
}

public String getId() {


return id;
}

public String getName() {


return name;
}

public String getSpecialty() {


return specialty;
}
public String getPhoneNumber() {
return phoneNumber;
}

public String getBio() {


return bio;
}

public String getAvatarUri() {


return avatarUri;
}
}

Crear esquema de Lawyer App


El esquema se establecerá en una clase donde definirás los nombres de tablas, columnas y
uris para un uso global.

Añade dentro del paquete data una nueva clase llamada LawyersContract y define una clase
interna con los datos de la tabla "lawyer" que se creará en la base de datos:
LawyersContract.java

/**
* Esquema de la base de datos para abogados
*/
public class LawyersContract {

public static abstract class LawyerEntry implements BaseColumns{


public static final String TABLE_NAME ="lawyer";

public static final String ID = "id";


public static final String NAME = "name";
public static final String SPECIALTY = "specialty";
public static final String PHONE_NUMBER = "phoneNumber";
public static final String AVATAR_URI = "avatarUri";
public static final String BIO = "bio";
}
}

En el anterior código podemos notar los siguientes detalles:

Creamos la clase interna LawyerEntry para guardar el nombre de las columnas de la tabla.
Se implementó la interfaz BaseColumns con el fin de agregar una columna extra que se
recomienda tenga toda tabla.
Estas declaraciones facilitan el mantenimiento del esquema, por si en algún momento
cambian los nombres de las tablas o columnas.
CREAR BASE DE DATOS EN SQLITE
El Android SDK nos provee una serie de clases para administrar nuestro archivo de base de
datos en SQLite.

Normalmente cuando conectamos otro gestor de bases de datos tenemos que validar
los datos del equipo, el usuario y el esquema, pero con SQLite no se requiere nada de eso, ya
que podemos trabajar directamente sobre la base de datos.
La clase que nos permitirá comunicar nuestra aplicación con la base de datos se
llama SQLiteOpenHelper. Se trata de una clase abstracta que nos provee los mecanismos
básicos para la relación entre la aplicación Android y la información.
Para implementar este controlador debes:

Crear una clase que extienda de SQLiteOpenHelper

Configurar un constructor apropiado


Sobrescribir los métodos onCreate() y onUpgrade()

Creando helper de abogados


1. Crea nueva clase que extienda de SQLiteOpenHelper y llamala LawyersDbHelper.
public class LawyersDbHelper extends SQLiteOpenHelper {
2. Escribe tú constructor y usa super para mantener la herencia del helper.

public static final int DATABASE_VERSION = 1;


public static final String DATABASE_NAME = "Lawyers.db";

public LawyersDbHelper(Context context) {


super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
Los parámetros del constructor tienen la siguiente finalidad:

Context context: Contexto de acción para el helper.


String name: Nombre del archivo con extensión .db, donde se almacenará la base de datos,
que a su vez corresponde al nombre de la base de datos.
CursorFactory factory: Asignamos null, por ahora no es necesario comprender el
funcionamiento de este parámetro.
int version: Entero que representa la versión de la base de datos. Su valor inicial por defecto
es 1. Si en algún momento la versión es mayor se llama al método onUpgrade() para actualizar
la base de datos a la nueva versión. Si es menor, se llama a downUpgrade() para volver a una
versión previa.
3. Sobrescribe el método onCreate().
Este método es llamado automáticamente cuando creamos una instancia de la
clase SQLiteOpenHelper. En su interior establecemos la creación de las tablas y registros.
Recibe como parámetro una referencia de la clase SQLiteDataBase, la cual actua como
manejadora de la base de datos.
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// Comandos SQL
}
Por defecto el archivo de la base de datos será almacenado en:

/data/data/<paquete>/databases/<nombre-de-la-bd>.db
4. Sobrescribe el método onUpgrade().
Este es ejecutado si se identificó que el usuario tiene una versión antigua de la base de datos.

En su interior establecerás instrucciones para modificar el esquema de la base de datos,


como por ejemplo eliminar todo el esquema y recrearlo, agregar una nueva tabla, añadir una
nueva columna, etc.

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// No hay operaciones
}
Recibe tres parámetros:

SQLiteDatabase db: Manejador de la base de datos.


int oldVersion: Se trata de un entero que indica la versión antigua de la base de datos.
int newVersion: Entero que se refiere a la versión nueva de la base de datos.
Código SQL para crear una base de datos
Una vez terminado el esquema, procede a crear la tabla de abogados en onCreate() con el
metodo execSQL() y el comando CREATE TABLE:
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE " + LawyerEntry.TABLE_NAME + " ("
+ LawyerEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ LawyerEntry.ID + " TEXT NOT NULL,"
+ LawyerEntry.NAME + " TEXT NOT NULL,"
+ LawyerEntry.SPECIALTY + " TEXT NOT NULL,"
+ LawyerEntry.PHONE_NUMBER + " TEXT NOT NULL,"
+ LawyerEntry.BIO + " TEXT NOT NULL,"
+ LawyerEntry.AVATAR_URI + " TEXT,"
+ "UNIQUE (" + LawyerEntry.ID + "))");

Este método ejecuta una sola sentencia SQL que no retorne en filas. Por lo que el
comando SELECT no es posible usarlo dentro de él.
Es recomendable que la llave primaria sea BaseColumns._ID, ya que el framework de Android
usa esta referencia internamente en varios procesos.
Sin embargo puedes usar tu propio ID y añadirle un índice UNIQUE para mantener la unicidad
de tus filas según tus reglas de negocio.

Evita ejecutar múltiples sentencias en una sola invocación del método execSQL(). Puede que
se ejecute la primera, pero las otras no surtirán efecto.

INSERTAR INFORMACIÓN EN LA BASE DE


DATOS
El método cuya funcionalidad es añadir filas a nuestras tablas se llama SQLiteDatabase.insert().
La receta a seguir para usarlo es:

1.Crea un objeto del tipo ContentValues. Este permite almacenar las columnas del registro en
pares clave-valor
2.Añade los pares con el método put()
3.Invoca a insert() a través de la instancia de la base de datos
Sus parámetros funcionan así:

String table: Nombre de la tabla donde se insertará la info.


String nullColumnHack: Nombre de una columna que acepta valores NULL y de la cual no se
proveen pares clave-valor en values.
ContentValues values: Conjunto de pares clave-valor para las columnas.
Ejemplo…

@Override
public void onCreate(SQLiteDatabase db) {
// Create table...

// Contenedor de valores
ContentValues values = new ContentValues();

// Pares clave-valor
values.put(LawyerEntry.ID, "L-001");
values.put(LawyerEntry.NAME, "Carlos solarte");
values.put(LawyerEntry.SPECIALTY, "Abogado penalista");
values.put(LawyerEntry.PHONE_NUMBER, "300 200 1111");
values.put(LawyerEntry.BIO, "Carlos es una profesional con 5 años de trayectoria...");
values.put(LawyerEntry.AVATAR_URI, "carlos_solarte.jpg");

// Insertar...
db.insert(LawyerEntry.TABLE_NAME, null, values);

Con esto en mente, simplifica el guardado de abogados creando un método


llamado saveLawyer(). Este recibirá una instancia Lawyer, se convertirá a ContentValues y luego
se insert
public long saveLawyer(Lawyer lawyer) {

SQLiteDatabase sqLiteDatabase = getWritableDatabase();

return sqLiteDatabase.insert(
LawyerEntry.TABLE_NAME,
null,
lawyer.toContentValues());

Usa getWritableDatabase() para obtener el manejador de la base de datos para operaciones de


escritura. En cuestiones de lectura usa getReadableDatabase().
El método toContentValues() es solo una traducción de pares:
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(LawyerEntry.ID, id);
values.put(LawyerEntry.NAME, name);
values.put(LawyerEntry.SPECIALTY, specialty);
values.put(LawyerEntry.PHONE_NUMBER, phoneNumber);
values.put(LawyerEntry.BIO, bio);
values.put(LawyerEntry.AVATAR_URI, avatarUri);
return values;
}

Podrías usar el comando execSQL() para ejecutar una sentencia INSERT, pero como estás
recibiendo datos externos, es mejor usar insert() para evitar inyecciones SQL.

Crear abogados de prueba


Inserta 8 registros de prueba en onCreate() para tener un mock funcional cuando creemos la
lista de abogados.
@Override
public void onCreate(SQLiteDatabase db) {
// create table

// Insertar datos ficticios para prueba inicial


mockData(db);

private void mockData(SQLiteDatabase sqLiteDatabase) {


mockLawyer(sqLiteDatabase, new Lawyer("Carlos Perez", "Abogado penalista",
"300 200 1111", "Gran profesional con experiencia de 5 años en casos penales.",
"carlos_perez.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Daniel Samper", "Abogado accidentes de tráfico",
"300 200 2222", "Gran profesional con experiencia de 5 años en accidentes de
tráfico.",
"daniel_samper.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Lucia Aristizabal", "Abogado de derechos
laborales",
"300 200 3333", "Gran profesional con más de 3 años de experiencia en defensa
de los trabajadores.",
"lucia_aristizabal.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Marina Acosta", "Abogado de familia",
"300 200 4444", "Gran profesional con experiencia de 5 años en casos de
familia.",
"marina_acosta.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Olga Ortiz", "Abogado de administración
pública",
"300 200 5555", "Gran profesional con experiencia de 5 años en casos en
expedientes de urbanismo.",
"olga_ortiz.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Pamela Briger", "Abogado fiscalista",
"300 200 6666", "Gran profesional con experiencia de 5 años en casos de derecho
financiero",
"pamela_briger.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Rodrigo Benavidez", "Abogado Mercantilista",
"300 200 1111", "Gran profesional con experiencia de 5 años en redacción de
contratos mercantiles",
"rodrigo_benavidez.jpg"));
mockLawyer(sqLiteDatabase, new Lawyer("Tom Bonz", "Abogado penalista",
"300 200 1111", "Gran profesional con experiencia de 5 años en casos penales.",
"tom_bonz.jpg"));
}

public long mockLawyer(SQLiteDatabase db, Lawyer lawyer) {


return db.insert(
LawyerEntry.TABLE_NAME,
null,
lawyer.toContentValues());
}

LEER INFORMACIÓN DE LA BASE DE DATOS


Para obtener los registros de nuestra tabla usaremos el método query().
query (String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy)

Por ejemplo, si quisiéramos consultar todos los datos de la tabla lawyer usaríamos el
siguiente código:

Cursor c = db.query(
LawyerEntry.TABLE_NAME, // Nombre de la tabla
null, // Lista de Columnas a consultar
null, // Columnas para la cláusula WHERE
null, // Valores a comparar con las columnas del WHERE
null, // Agrupar con GROUP BY
null, // Condición HAVING para GROUP BY
null // Cláusula ORDER BY
);

Este método te ayuda a añadir todas las partes posibles de las cuales se podría componer
una consulta, además que te protege de inyecciones SQL, separando las cláusulas de los
argumentos.
Los parámetros tienen los siguientes propósitos:
String table: Nombre de la tabla a consultar
String[] columns: Lista de nombres de las columnas que se van a consultar. Si deseas obtener
todas las columnas usas null.
String selection: Es el cuerpo de la sentencia WHERE con las columnas a condicionar. Es
posible usar el placeholder '?' para generalizar la condición.
String[] selectionArgs: Es una lista de los valores que se usaran para reemplazar las incógnitas
de selection en el WHERE.
String groupBy: Aquí puedes establecer cómo se vería la cláusula GROUP BY, si es que la
necesitas.
String having: Establece la sentencia HAVING para condicionar a groupBy.
String orderBy: Reordena las filas de la consulta a través de ORDER BY.
Debido a la simplicidad de nuestra consulta anterior, la mayoría de parámetros fueron null, ya
que se consultan todas las columnas de la tabla y todos los registros.
Pero si quisieras consultar el nombre del abogado con el id "L-001", tendrías que usar la
siguiente cláusula WHERE:
String columns[] = new String[]{LawyerEntry.NAME};
String selection = LawyerEntry.ID + " LIKE ?"; // WHERE id LIKE ?
String selectionArgs[] = new String[]{"L-001"};

Cursor c = db.query(
LawyerEntry.TABLE_NAME,
columns,
selection,
selectionArgs,
null,
null,
null
);

Ahora, existe otro método alternativo para realizar consultas llamado rawQuery(). Con él pasas
como parámetro un String del código SQL de la consulta.
Veamos:

db.rawQuery("select * from " + LawyerEntry.TABLE_NAME, null);

Si deseas crear una consulta generalizada usa el placeholder '?' en la cláusula WHERE. Luego
asigna los valores a cada incógnita en el segundo parámetro:
String query = "select * from " + LawyerEntry.TABLE_NAME + " WHERE _id=?";
database.rawQuery(query, new String[]{"3"});

Cursores en SQLite
Tanto query() como rawQuery() retornan un objeto de tipo Cursor.
Este objeto es un apuntador al conjunto de valores obtenidos de la consulta. Al inicio el
cursor apunta a una dirección previa a la primera fila. Por lo que debes leer cada tupla
moviendo el cursor a la fila siguiente.

Emplea el método booleano moveToNext() para avanzar al siguiente registro. Este


retorna true si fue posible o false si ya no existen más elementos.
Este concepto es similar a cuando vimos cursores en SQL Server o en MySQL. Donde
recorríamos cada elemento con un bucle while hasta que ya no existieran más elementos que
referenciar.
Por ejemplo…

while(c.moveToNext()){
String name = c.getString(c.getColumnIndex(LawyerEntry.NAME));
// Acciones...
}

Usa métodos get*() para obtener el valor de cada columna a través del índice según su tipo de
dato. Es decir, obtienes enteros con getInt(), flotantes con getFloat(), etc.
El índice de la columna se obtiene con getColumnsIndex().
Leer abogados de la base de datos
Puedes aprovechar este nuevo concepto e implementar un método de lectura para todos los
abogados (getAllLawyers()) y otro por ID (getLawyerById()).
La diferencia estaría en la lectura por id requiere ese elemento como parámetro…

public Cursor getAllLawyers() {


return getReadableDatabase()
.query(
LawyerEntry.TABLE_NAME,
null,
null,
null,
null,
null,
null);
}

public Cursor getLawyerById(String lawyerId) {


Cursor c = getReadableDatabase().query(
LawyerEntry.TABLE_NAME,
null,
LawyerEntry.ID + " LIKE ?",
new String[]{lawyerId},
null,
null,
null);
return c;
}

Crear lista de abogados


1. Crea un paquete nuevo llamado lawyers y haz click derecho sobre este. Selecciona New >
Activity > Basic Activity para llamar al asistente de creación y configura los datos de la
activity así:
2. Abre el layout activity_lawyers.xml y simplifica su contenido a una AppBar junto al
contenido principal:

activity_lawyers.xml

<?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"
android:fitsSystemWindows="true"
tools:context=".lawyers.LawyersActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_lawyers" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:tint="@android:color/white"
app:fabSize="normal"
app:srcCompat="@drawable/ic_account_plus" />
</android.support.design.widget.CoordinatorLayout>

3. Abre el archivo que se hace referencia en la etiqueta <include> del layout de la actividad de
abogados y agrega el identificador lawyers_container al nodo principal. Esto con el fin de tener
la referencia del contenedor donde se agregará un fragmento posterior.

content_lawyers.xml

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout 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:id="@+id/lawyers_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".lawyers.LawyersActivity"
tools:showIn="@layout/activity_lawyers"/>

Agregar fragmento de abogados

1. En mismo paquete anterior presiona click derecho y selecciona New > Fragment >
Fragment (Blank).
Nombralo LawyersFragment y configura las siguientes características así:

Abre el código prefabricado que te aparezca y límpialo para que te quede así:
IMAGEN PENDIENTE

LawyersFragment.java

/**
* Vista para la lista de abogados del gabinete
*/
public class LawyersFragment extends Fragment {

public LawyersFragment() {
// Required empty public constructor
}

public static LawyersFragment newInstance() {


return new LawyersFragment();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_lawyers, container, false);

return root;
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

La aparición del método onActivityResult() se debe a que la lista que tendrá el fragmento debe
refrescarse en caso de que las screens de inserción o detalle hayan producido una
modificación de la tabla lawyer.
Como vimos en el artículo de comunicaciones entre actividades, una actividad la cual se
espera decida un resultado debe ser llamada con startActivityForResult().

2. Agrega un ListView al layout fragment_lawyers.xml para crear una lista en la interfaz.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".lawyers.LawyersFragment">

<ListView
android:id="@+id/lawyers_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null"/>

</FrameLayout>

3. El siguiente paso es crear el diseño de los ítems de la lista.


Si retomas el wireframe visto al inicio del tutorial, verás que la foto de perfil del abogado está
al lado izquierdo de la distribución y justo a su derecha va el nombre completo.

Llevándolo a un mock de alta definición tendrás:


IMAGEN PENDIENTE

Con ello en mente, crea un nuevo layout llamado list_item_lawyer.xml y agrega la


siguiente definición XML:

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout 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:minHeight="?listPreferredItemHeight"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="8dp">

<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="72dp"
android:text="New Text"
android:textAppearance="?textAppearanceListItem"
tools:text="Carlos Giron" />

<ImageView
android:id="@+id/iv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_account_circle" />
</RelativeLayout>
4. Ve a la actividad LawyersActivity y realiza una transacción del tipo add() para insertar el
fragmento en el contenedor principal.
public class LawyersActivity extends AppCompatActivity {
public static final String EXTRA_LAWYER_ID = "extra_lawyer_id";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lawyers);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

LawyersFragment fragment = (LawyersFragment)


getSupportFragmentManager().findFragmentById(R.id.lawyers_container);

if (fragment == null) {
fragment = LawyersFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.lawyers_container, fragment)
.commit();
}
}
}

Crear adaptador con cursor


Existe un adaptador especial para el manejo de bases de datos llamado CursorAdapter. Esta
clase permite poblar una lista a través de un cursor.
CursorAdapter es una clase abstracta de la cual se ha de crear tu adaptador personalizado.

Con ArrayAdapter teníamos que sobrescribir el método getView() para inflar nuestras filas con
los datos de la lista.
Pero no es el caso con CursorAdapter. Esta vez debemos sobrescribir dos métodos aislados
llamados bindView() y newView().
bindView() es el encargado de poblar la lista con los datos del cursor y newView() es quien infla

cada view de la lista. Al implementar ambos métodos no debemos preocuparnos por iterar el
curso, esto es manejado internamente.
Adaptador de abogados
1. Escribe nuestra nueva clase llamada LawyersCursorAdapter y extiendela de CursorAdapter.
/**
* Adaptador de abogados
*/
public class LawyersCursorAdapter extends CursorAdapter {

2. Agrega un constructor que transmita los parámetros a través de super para mantener la
herencia.
public LawyersCursorAdapter(Context context, Cursor c) {
super(context, c, 0);
}

3. Implementa newView() para acceder a la instancia del LayoutInflater a través de context y


luego invoca inflate() para inflar el layout del ítem.

@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
LayoutInflater inflater = LayoutInflater.from(context);
return inflater.inflate(R.layout.list_item_lawyer, viewGroup, false);
}

4. Implementa bindView() para obtener el valor de las columnas name y avatarUri. Luego
setealos en los views del layout.

@Override
public void bindView(View view, final Context context, Cursor cursor) {

// Referencias UI.
TextView nameText = (TextView) view.findViewById(R.id.tv_name);
final ImageView avatarImage = (ImageView) view.findViewById(R.id.iv_avatar);

// Get valores.
String name = cursor.getString(cursor.getColumnIndex(LawyerEntry.NAME));
String avatarUri = cursor.getString(cursor.getColumnIndex(LawyerEntry.AVATAR_URI));

// Setup.
nameText.setText(name);
Glide
.with(context)
.load(Uri.parse("file:///android_asset/" + avatarUri))
.asBitmap()
.error(R.drawable.ic_account_circle)
.centerCrop()
.into(new BitmapImageViewTarget(avatarImage) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable drawable
= RoundedBitmapDrawableFactory.create(context.getResources(),
resource);
drawable.setCircular(true);
avatarImage.setImageDrawable(drawable);
}
});

}
El singleton Glide hace parte de una librería con el mismo nombre, cuyo objetivo es cargar
imágenes de forma eficiente.
Básicamente ese código carga la imagen desde la carpeta assets en forma de Bitmap sobre el
view avatarImage.

Otra alternativa para adaptadores con cursor


Si el diseño de las filas de tu lista es sencillo (uno o dos text views), entonces la
clase SimpleCursorAdapter podría ahorrarte la escritura de un adaptador personalizado.
Esta es una subclase de CursorAdapter, que posee una implementación completa para los
android developers.
Con ella no debes sobrescribir métodos ni crear un archivo de diseño, ya que permite usar
layouts del sistema.

Por ejemplo…
Crear un SimpleCursorAdapter para mostrar el nombre y especialidad de los abogados.
Solución
Usa el layout del sistema two_line_list_item en el constructor del adaptador:

//Iniciando el nuevo Adaptador


mLawyersAdapter = new SimpleCursorAdapter(
getActivity(), // Context context
android.R.layout.two_line_list_item, // int layout
mLawyersDbHelper.getAllLawyers(), // Cursor c
new String[]{LawyerEntry.NAME, LawyerEntry.SPECIALTY}, // String[] from
new int[]{android.R.id.text1, android.R.id.text2}, // int[] to
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER // int flags
);

mLawyersList.setAdapter(mLawyersAdapter);

El constructor se configura así:


Context context: Es el contexto donde se encuentra la lista.
int layout: Es el layout que usaremos para inflar los elementos de cada fila. En nuestro caso
usamos el prefabricado por Android para los elementos con dos text views.
Cursor c: Es el cursor que representa el origen de datos para el adaptador. Asignamos el
cursor hacia todos los registros de la tabla lawyer.
String[]from: Es un arreglo de strings que contiene el nombre de las columnas a consultar.
int[] to: Es un arreglo de enteros con las referencias directas de los text views en el layout.
Deben tener el mismo orden que las columnas. Los textos dentro
de two_line_list_item.xml se llaman text1 y text2 respectivamente.
int flags: Es una bandera para establecer el comportamiento del
adaptador. FLAG_REGISTER_CONTENT_OBSERVER registra un observador adherido al cursor para
saber cuándo cambio su información y así refrescar la lista.
Cargar datos del cursor a la lista
Dentro del fragmento LawyersFragment:

1. En onCreateView() obtén la referencia de la lista de abogados y setea un nuevo adaptador.


Crea una instancia del helper y escribe un método loadLawyers() que será llamado para cargar
los datos.
private LawyersDbHelper mLawyersDbHelper;
private ListView mLawyersList;
private LawyersCursorAdapter mLawyersAdapter;
private FloatingActionButton mAddButton;

public LawyersFragment() {
// Required empty public constructor
}

public static LawyersFragment newInstance() {


return new LawyersFragment();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_lawyers, container, false);

// Referencias UI
mLawyersList = (ListView) root.findViewById(R.id.lawyers_list);
mLawyersAdapter = new LawyersCursorAdapter(getActivity(), null);
mAddButton = (FloatingActionButton) getActivity().findViewById(R.id.fab);

// Setup
mLawyersList.setAdapter(mLawyersAdapter);

// Instancia de helper
mLawyersDbHelper = new LawyersDbHelper(getActivity());

// Carga de datos
loadLawyers();

return root;
}

private void loadLawyers() {


// Cargar datos...
}
2. Crea una tarea asíncrona dentro del fragmento, la cuál reciba como resultado un Cursor.
Esto con el fin de no entorpecer el hilo principal con el acceso a la base de datos.
Sobrescribe doInBackground() para usar el método getAllLawyers() y luego cambia el cursor del
adaptador en onPostExecute() con swapCursor().

private class LawyersLoadTask extends AsyncTask<Void, Void, Cursor> {


@Override
protected Cursor doInBackground(Void... voids) {
return mLawyersDbHelper.getAllLawyers();
}

@Override
protected void onPostExecute(Cursor cursor) {
if (cursor != null && cursor.getCount() > 0) {
mLawyersAdapter.swapCursor(cursor);
} else {
// Mostrar empty state
}
}
}

3. Ejecuta la tarea dentro de loadLawyers():

private void loadLawyers() {


new LawyersLoadTask().execute();
}

Incrustar fragmento de abogados


Abre LawyersActivity y escribe una transacción de fragmentos del tipo add() con la referencia
de LawyersFragment:
LawyersActivity.java

public class LawyersActivity extends AppCompatActivity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lawyers);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

LawyersFragment fragment = (LawyersFragment)


getSupportFragmentManager().findFragmentById(R.id.lawyers_container);

if(fragment==null){
fragment = LawyersFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.lawyers_container, fragment)
.commit();
}
}
}

Si corres la app tendrás el siguiente estado:


IMAGEN PENDIENTE

Mostrar detalle de abogados


La screen del detalle se muestra cuando el usuario pulse uno de los ítems de la lista.

En ella se proyectarán el resto de datos del abogado para mostrar la entidad completa.

Crear actividad de detalle


1. Crea un nuevo paquete llamado lawyerdetail y presiona click derecho sobre este. Ve a New >
Activity > Scrolling Activity y nombra al archivo LawyerDetailActivity.
La configuración debería quedarte así:

IMAGEN PENDIENTE

2. Abre el layout de la actividad y simplifícalo a una App Bar y el contenido principal.


Debido a que se usará scroll con la foto de perfil del abogado, incluye un
elemento <ImageView> por encima de la toolbar.
activity_lawyer_detail.xml
<?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"
android:fitsSystemWindows="true"
tools:context=".lawyerdetail.LawyerDetailActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<!-- Imagen del detalle -->


<ImageView
android:id="@+id/iv_avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_lawyer_detail" />

</android.support.design.widget.CoordinatorLayout>
3. En el layout referenciado por el componente <include>, deja tan solo un
nodo RelativeLayout y márcalo con el identificador lawyer_detail_container.
content_lawyer_detail.xml

<?xml version="1.0" encoding="utf-8"?>


<android.support.v4.widget.NestedScrollView
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:id="@+id/lawyer_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".lawyerdetail.LawyerDetailActivity"
tools:showIn="@layout/activity_lawyer_detail">

</android.support.v4.widget.NestedScrollView>

Crear el fragmento de detalle de abogados


1. Crea en el paquete lawyerdetail un nuevo fragmento llamado LawyerDetailFragment y genera la
siguiente configuración:

IMAGEN PENDIENTE

Para saber que detalle vamos a consultar, es necesario tener el ID del abogado que será
consultado en la base de datos. Así que en el método de fabricación newInstance() incluye tan
solo un parámetro String para este cometido.

LawyerDetailFragment.java

/**
* Vista para el detalle del abogado
*/
public class LawyerDetailFragment extends Fragment {

private String mLawyerId;

private CollapsingToolbarLayout mCollapsingView;


private ImageView mAvatar;
private TextView mPhoneNumber;
private TextView mSpecialty;
private TextView mBio;

private LawyersDbHelper mLawyersDbHelper;

public LawyerDetailFragment() {
// Required empty public constructor
}

public static LawyerDetailFragment newInstance(String lawyerId) {


LawyerDetailFragment fragment = new LawyerDetailFragment();
Bundle args = new Bundle();
args.putString(ARG_LAWYER_ID, lawyerId);
fragment.setArguments(args);
return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (getArguments() != null) {
mLawyerId = getArguments().getString(ARG_LAWYER_ID);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_lawyer_detail, container, false);
mCollapsingView = (CollapsingToolbarLayout)
getActivity().findViewById(R.id.toolbar_layout);
mAvatar = (ImageView) getActivity().findViewById(R.id.iv_avatar);
mPhoneNumber = (TextView) root.findViewById(R.id.tv_phone_number);
mSpecialty = (TextView) root.findViewById(R.id.tv_specialty);
mBio = (TextView) root.findViewById(R.id.tv_bio);

mLawyersDbHelper = new LawyersDbHelper(getActivity());

return root;
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Acciones
}

Se requiere onActivityResult() ya que cuando se intente eliminar o editar el abogado se estará


pendiente de cambios en la base de datos, así que podremos reportar a LawyersFragment la
necesidad de actualizar la lista.
2. La interfaz de usuario del detalle se basa en el patrón Flexible space with image de
las técnicas de scrolling de Material Design, donde se contraen la App Bar extendida para
desplegar el contenido inferior de la pantalla.
El wireframe inicial mostraba la foto de perfil del abogado en la app bar y por debajo una serie
de pares etiqueta-contenido con los demás datos.

Basado en esa idea, intenta recrear el siguiente mock con la organización que desees:

IMAGEN PENDIENTE

En mi caso el resultado final me quedó así:


fragment_lawyer_detail.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="72dp"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="Teléfono"
android:textColor="?colorPrimary" />

<TextView
android:id="@+id/tv_phone_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="300 20 1111" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="Especialidad"
android:textColor="?colorPrimary" />

<TextView
android:id="@+id/tv_specialty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="Abogado penalista" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="Biografía"
android:textColor="?colorPrimary" />

<TextView
android:id="@+id/tv_bio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="@string/large_text" />

</LinearLayout>
3. Abre la actividad contenedora y realiza una transacción de agregación en onCreate().
Ten en cuenta que la instancia del fragmento recibe el id del abogado.

Este debe venir en un Intent explicito desde LawyersActivity y ser de tipo String. Así que crea
una constante en la actividad de lista para la clave del extra. Luego obtenla
con getIntent() en LawyerDetailActivity.
public class LawyerDetailActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lawyer_detail);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

String id = getIntent().getStringExtra(LawyersActivity.EXTRA_LAWYER_ID);

LawyerDetailFragment fragment = (LawyerDetailFragment)


getSupportFragmentManager().findFragmentById(R.id.lawyer_detail_container)
;
if (fragment == null) {
fragment = LawyerDetailFragment.newInstance(id);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.lawyer_detail_container, fragment)
.commit();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_lawyer_detail, menu);
return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}

}
Obtener abogado por id
Carga el detalle del abogado con el método getLawyerById() con una tarea asíncrona. Llámala
en onCreateView() a través de un método loadLawyer().
En onPostExecute() extrae cada uno de los valores de la columna y asígnalos en los views de
texto para poblar el detalle.
public class LawyerDetailFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// más...

mLawyersDbHelper = new LawyersDbHelper(getActivity());

loadLawyer();

return root;
}

private void loadLawyer() {


new GetLawyerByIdTask().execute();
}

private void showLawyer(Lawyer lawyer) {


mCollapsingView.setTitle(lawyer.getName());
Glide.with(this)
.load(Uri.parse("file:///android_asset/" + lawyer.getAvatarUri()))
.centerCrop()
.into(mAvatar);
mPhoneNumber.setText(lawyer.getPhoneNumber());
mSpecialty.setText(lawyer.getSpecialty());
mBio.setText(lawyer.getBio());
}

private void showLoadError() {


Toast.makeText(getActivity(),
"Error al cargar información", Toast.LENGTH_SHORT).show();
}

private class GetLawyerByIdTask extends AsyncTask<Void, Void, Cursor> {

@Override
protected Cursor doInBackground(Void... voids) {
return mLawyersDbHelper.getLawyerById(mLawyerId);
}

@Override
protected void onPostExecute(Cursor cursor) {
if (cursor != null && cursor.moveToLast()) {
showLawyer(new Lawyer(cursor));
} else {
showLoadError();
}
}
}

Fíjate que usé un constructor nuevo de la clase Lawyer, donde se recibe un cursor. Su función
es fabricar un nuevo abogado:
public Lawyer(Cursor cursor) {
id = cursor.getString(cursor.getColumnIndex(LawyerEntry.ID));
name = cursor.getString(cursor.getColumnIndex(LawyerEntry.NAME));
specialty = cursor.getString(cursor.getColumnIndex(LawyerEntry.SPECIALTY));
phoneNumber = cursor.getString(cursor.getColumnIndex(LawyerEntry.PHONE_NUMBER));
bio = cursor.getString(cursor.getColumnIndex(LawyerEntry.BIO));
avatarUri = cursor.getString(cursor.getColumnIndex(LawyerEntry.AVATAR_URI));
}

Iniciar actividad de detalle al pulsar ítem de lista


1. Abre LawyersFragment y agrega una escucha OnItemClickListener a la lista.
// Eventos
mLawyersList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
// ...
}
});
2. Procesa el controlador onItemClick() para extraer el id del elemento seleccionado.
Usa getItem()del adaptador para conseguir el ítem pulsado.

Cursor currentItem = (Cursor) mLawyersAdapter.getItem(i);


String currentLawyerId = currentItem.getString(
currentItem.getColumnIndex(LawyerEntry.ID));

showDetailScreen(currentLawyerId);

3. Con el ID obtenido inicia la actividad de detalle.

private void showDetailScreen(String lawyerId) {


Intent intent = new Intent(getActivity(), LawyerDetailActivity.class);
intent.putExtra(LawyersActivity.EXTRA_LAWYER_ID, lawyerId);
startActivityForResult(intent, REQUEST_UPDATE_DELETE_LAWYER);
}

REQUEST_UDAPTE_DELETE_LAWYER es una constante entera que representa la vía de


comunicación entre la screen de abogados y la de detalles.
4. Actualiza la lista en onActivityResult() si el resultado fue positivo:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (Activity.RESULT_OK == resultCode) {
switch (requestCode) {
case REQUEST_UPDATE_DELETE_LAWYER:
loadLawyers();
break;
}
}
}

La Herramienta Sqlite3
sqlite3 es una herramienta de administración para nuestras bases de datos SQLite a través
de la línea de comandos. Normalmente puedes descargarla de la página oficial de SQLite,
pero tanto como la distribución de Android y Android Studio ya la traen consigo.
Antes de ejecutarla en el dispositivo, primero usaremos la herramienta Android Device
Monitor del SDK, la cual permite visualizar las características del dispositivo que se está
ejecutando. En ella podemos visualizar estadísticas de rendimiento, monitorear recursos y
navegar por el sistema de archivos.
Si deseas ejecutarla solo presiona el siguiente icono en Android Studio:

IMAGEN PENDIENTE

Ahora dirígete a la pestaña “File Explorer”

IMAGEN PENDIENTE

Como ves, se visualizan todos los directorios que se encuentran en el dispositivo. Así que
para ver si existe nuestro archivo de base de datos, iremos a la ruta de la cual hablamos al
inicio /data/data/<paquete>/databases/Lawyers.db

IMAGEN PENDIENTE

Si todo salió bien, veremos nuestro archivo Lawyers.db.


Ahora guárdalo con el botón de la parte superior derecha denominado “Pull a file from the
device“. En la siguiente sección veremos algo interesante con él.

IMAGEN PENDIENTE

Ya que hemos comprobado que existe nuestra base de datos, iniciaremos sqlite3 dentro del
dispositivo.

Sigue los siguientes pasos:

1. Inicia el terminal de Windows (cmd) o usa la pestaña Terminal de Android Studio

IMAGEN PENDIENTE

2. Navega hasta el directorio platform-tools del SDK de Android.

La sintaxis de la dirección por defecto es:

<sdk>/platform-tools/
Recuerda que para navegar a través de carpetas en DOS se utiliza el comando cd.
3. Una vez hayas encontrado el directorio, digita la siguiente linea de comandos:

adb shell

Este comando conecta remotamente la consola de comandos del dispositivo Android con tu
consola local. Cuando ya estés conectado a la consola del AVD, verás en el terminal algo
como esto:

root@android:/ #

4. Inicia sqlite3 en el dispositivo con el siguiente comando:


sqlite3 data/data/<package>/databases/<nombre-base-de-datos>

La anterior instrucción accede a sqlite3 y al mismo tiempo le pide que abra la base de datos
expuesta en el directorio especificado. Si accedió a la base de datos verás los siguientes
mensajes:
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".help" for usage hints.
sqlite>

5. Usa el comando .schema para ver el resumen del esquema de la base de datos:

CREATE TABLE android_metadata


(
locale TEXT
);

CREATE TABLE lawyer


(
_id INTEGER PRIMARY KEY autoincrement,
id text NOT NULL,
name text NOT NULL,
specialty text NOT NULL,
phonenumber text NOT NULL,
bio text NOT NULL,
avataruri text,
UNIQUE (id)
);

El log muestra que la tabla lawyer ha sido creada correctamente.


La tabla llamada android_metadata es parte de la configuración local de la base de datos, por lo
que siempre la encontrarás.

Usar sqlite3 en estación de trabajo


Otra forma de comprobar el esquema de nuestra base de datos es usar sqlite3.exe en
nuestro equipo local.
1. Ve a la ruta para encontrar la carpeta platform-tools del SDK de Android y ejecuta la
aplicación.
2. Luego usa .open para abrir el archivo en una ruta especificada o copia y pega el
archivo Lawyers.db en la carpeta:
sqlite>.open Lawyers.db

3. Finalmente usa .schema y tendrás el mismo resultado anterior.


SQLite App Browser
Si deseas conocer un SQL Manager más visual, entonces SQLite Browser es una opción que
te gustaría considerar.
Se trata de un editor para archivos de bases de datos SQLite de código abierto y súper
sencillo de usar.

Solo basta con iniciarlo en tu pc y arrastrar el archivo Lawyers.db a su editor.

Inmediatamente nos mostrará el esquema en forma de tablas con interfaz gráfica de usuario.
Además de permitir editar la estructura y ejecutar sentencias SQL dentro de ella.

IMAGEN PENDIENTE

Borrar Información De La Base De Datos

Eliminar registros es muy sencillo, solo tenemos que usar el método delete().
Recibe como parámetros el nombre de la tabla, el estilo de la selección de la
cláusula WHERE y los valores de comparación para determinar que filas borrar.

Por ejemplo…

Eliminar el abogado que donde _id = 3:

String selection = LawyerEntry._ID + " = ?";


String[] selectionArgs = {"3"};

db.delete(
LawyerEntry.TABLE_NAME,
selection,
selectionArgs);

Crea un método de eliminación de abogados en LawyersDbHelper llamado deleteLawyer():

public int deleteLawyer(String lawyerId) {


return getWritableDatabase().delete(
LawyerEntry.TABLE_NAME,
LawyerEntry.ID + " LIKE ?",
new String[]{lawyerId});
}

Eliminar un abogado
La eliminación y edición van como action buttons en la Toolbar de la actividad de detalle.
1. Para agregarlos ve a res/menu y abre menu_lawyer_detail.xml.

Agrega dos nodos <item>. El primero con el título de edición y el segundo refiriendose a la
eliminación

<menu 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"
tools:context=".lawyerdetail.LawyerDetailActivity">
<item
android:id="@+id/action_edit"
android:orderInCategory="1"
android:title="@string/action_edit"
android:icon="@drawable/ic_pencil"
app:showAsAction="ifRoom" />

<item
android:id="@+id/action_delete"
android:orderInCategory="2"
android:icon="@drawable/ic_delete"
android:title="@string/action_delete"
app:showAsAction="ifRoom" />
</menu>

2. Habilita la contribución de LawyerDetailFragment a la Toolbar con el


método setHasOptionsMenu()con el valor de true en onCreate().

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// ...

setHasOptionsMenu(true);
}
3. Implementa el método onOptionsItemSelected() en el fragmento. En el abre una
estructura switch y procesa los casos de edición y eliminación.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_edit:
showEditScreen();
break;
case R.id.action_delete:
new DeleteLawyerTask().execute();
break;
}
return super.onOptionsItemSelected(item);
}

4. Para manejar el evento de borrado, crea una nueva tarea asíncrona que llame
a DeleteLawyerTask.
En doInBackground() llama a LawyersDbHelper.deleteLawyer().

private class DeleteLawyerTask extends AsyncTask<Void, Void, Integer> {


@Override
protected Integer doInBackground(Void... voids) {
return mLawyersDbHelper.deleteLawyer(mLawyerId);
}

@Override
protected void onPostExecute(Integer integer) {
showLawyersScreen(integer > 0);
}

}
En postExecute() cierra la actividad de detalle con un resultado favorable hacia la actividad de
abogados en caso de que la eliminación fuese exitosa.

private void showLawyersScreen(boolean requery) {


if (!requery) {
showDeleteError();
}
getActivity().setResult(requery ? Activity.RESULT_OK : Activity.RESULT_CANCELED);
getActivity().finish();
}

De lo contrario muestra un error:

private void showDeleteError() {


Toast.makeText(getActivity(),
"Error al eliminar abogado", Toast.LENGTH_SHORT).show();
}
5. El evento de edición debe iniciar la actividad que crearemos ahora, pero puedes dejarla
expresada a través de un nuevo método llamado showAddEditScreen(). En iniciarás la actividad
de edición con un extra cuyo valor sea el ID del abogado.

private void showEditScreen() {


Intent intent = new Intent(getActivity(), AddEditLawyerActivity.class);
intent.putExtra(LawyersActivity.EXTRA_LAWYER_ID, mLawyerId);
startActivityForResult(intent, LawyersFragment.REQUEST_UPDATE_DELETE_LAWYER);
}

ACTUALIZAR INFORMACIÓN DE LA BASE DE


DATOS
En este caso usaremos el método update(). Es exactamente el mismo estilo de uso que los
anteriores métodos.
Especificaremos:

Nombre de la tabla
Valores nuevos
Instrucción WHERE
Argumentos del WHERE

Por ejemplo…

// Valores
ContentValues values = new ContentValues();

// Valores nuevos del nombre y teléfono


values.put(LawyerEntry.NAME, "Fracisco Palomino");
values.put(LawyerEntry.PHONE_NUMBER, "222 222 2222");

// WHERE
String selection = LawyerEntry.ID + " LIKE ?";
String[] selectionArgs = {"L-009"};

// Actualizar
db.update(
LawyerEntry.TABLE_NAME,
values,
selection,
selectionArgs);
Actualizar abogados
Dentro de LawyersDbHelper crea un nuevo método llamado updateLawyer(), cuyos parámetros
sean un objeto Lawyer y un string con el ID a modificar.
Su propósito es convertir el POJO en ContentValues y luego llamar a update():
public int updateLawyer(Lawyer lawyer, String lawyerId) {
return getWritableDatabase().update(
LawyerEntry.TABLE_NAME,
lawyer.toContentValues(),
LawyerEntry.ID + " LIKE ?",
new String[]{lawyerId}
);
}

Screen de creación de abogados


Aún no hemos creado la screen para la creación de abogados. Recuerda que esta también
servirá para la edición así que veamos cómo será la implementación:

Crear actividad para añadir/editar abogados


1. Para aislar esta caracteristica crea un nuevo paquete con el nombre addeditlawyer. Dentro
de él agrega una nueva actividad del tipo Basic Activity denominada AddEditLawyerActivity y
configurala así:

IMAGEN PENDIENTE

2. En su layout activity_add_edit_lawyer.xml modifica el fab button que viene por defecto para
que traiga un tamaño normal y su icono sea una marca de check.

Este será el encargado de guarda nuevos registros o los cambios a uno existente.

<?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"
android:fitsSystemWindows="true"
tools:context=".addeditlawyer.AddEditLawyerActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_add_edit" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:tint="@android:color/white"
app:fabSize="normal"
app:srcCompat="@drawable/ic_check" />

</android.support.design.widget.CoordinatorLayout>

3. En el layout de contenido, modifica al identificador del nodo principal con el


valor add_edit_lawyer_container.

content_add_edit.xml
<?xml version="1.0" encoding="utf-8"?>

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout 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:id="@+id/add_edit_lawyer_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".addeditlawyer.AddEditLawyerActivity"
tools:showIn="@layout/activity_add_edit">

</RelativeLayout>
Crear fragmento para añadir/editar abogados

1. Crea un nuevo fragmento con el nombre AddEditLawyerFragment. Su configuración sería:

IMAGEN PENDIENTE

Este fragmento cuando actúa como editor requiere el identificador del abogado, para una
carga previa de la información que se cargará en el formulario.

Así que en el método newInstance() deja un argumento String como se hizo


en LawyerDetailFragment:

AddEditLawyerFragment.java

/**
* Vista para creación/edición de un abogado
*/
public class AddEditLawyerFragment extends Fragment {
private static final String ARG_LAWYER_ID = "arg_lawyer_id";

private String mLawyerId;

private LawyersDbHelper mLawyersDbHelper;

private FloatingActionButton mSaveButton;


private TextInputEditText mNameField;
private TextInputEditText mPhoneNumberField;
private TextInputEditText mSpecialtyField;
private TextInputEditText mBioField;
private TextInputLayout mNameLabel;
private TextInputLayout mPhoneNumberLabel;
private TextInputLayout mSpecialtyLabel;
private TextInputLayout mBioLabel;

public AddEditLawyerFragment() {
// Required empty public constructor
}

public static AddEditLawyerFragment newInstance(String lawyerId) {


AddEditLawyerFragment fragment = new AddEditLawyerFragment();
Bundle args = new Bundle();
args.putString(ARG_LAWYER_ID, lawyerId);
fragment.setArguments(args);
return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mLawyerId = getArguments().getString(ARG_LAWYER_ID);
}
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_add_edit_lawyer, container, false);

// Referencias UI
mSaveButton = (FloatingActionButton) getActivity().findViewById(R.id.fab);
mNameField = (TextInputEditText) root.findViewById(R.id.et_name);
mPhoneNumberField = (TextInputEditText) root.findViewById(R.id.et_phone_number);
mSpecialtyField = (TextInputEditText) root.findViewById(R.id.et_specialty);
mBioField = (TextInputEditText) root.findViewById(R.id.et_bio);
mNameLabel = (TextInputLayout) root.findViewById(R.id.til_name);
mPhoneNumberLabel = (TextInputLayout) root.findViewById(R.id.til_phone_number);
mSpecialtyLabel = (TextInputLayout) root.findViewById(R.id.til_specialty);
mBioLabel = (TextInputLayout) root.findViewById(R.id.til_bio);

// Eventos
mSaveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
addEditLawyer();
}
});

mLawyersDbHelper = new LawyersDbHelper(getActivity());

// Carga de datos
if (mLawyerId != null) {
loadLawyer();
}
return root;
}

private void loadLawyer() {


// AsyncTask
}

}
Fijate que onCreateView() se verifica el contenido del ID del abogado para determinar si se
cargan los datos de un elemento existente.
2. El diseño de la interfaz para el formulario de añadir/editar consta de cuatro campos de
texto para la obtención de los datos: Nombre, Especialidad, Número de telefono y Biografía.
El avatar no lo capturaremos ya que requiere un proceso extra que se escapa del alcance de
este artículo.

El mock de alto nivel para la screen se vería así:

IMAGEN PENDIENTE

Es muy sencillo, ya que está basada en EditTexts organizados de forma vertical.

Si quieres usar etiquetas flotantes usa el wrapper TextInputLayout junto a la variación


derivada TextInputEditText:

fragment_add_edit_lawyer.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:orientation="vertical"
tools:context=".addeditlawyer.AddEditLawyerFragment">

<android.support.design.widget.TextInputLayout
android:id="@+id/til_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin">
<android.support.design.widget.TextInputEditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nombre"
android:inputType="textPersonName"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
tools:text="Alejandro Riascos" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/til_phone_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin">

<android.support.design.widget.TextInputEditText
android:id="@+id/et_phone_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Número de teléfono"
android:inputType="phone"
tools:text="300 200 4564" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/til_specialty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin">

<android.support.design.widget.TextInputEditText
android:id="@+id/et_specialty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Especialidad"
android:inputType="text"
tools:text="Abogado penalista" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/til_bio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin">

<android.support.design.widget.TextInputEditText
android:id="@+id/et_bio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top|start"
android:hint="Biografía"
android:imeOptions="actionDone"
android:inputType="text|textMultiLine"
tools:text="@string/lorem" />
</android.support.design.widget.TextInputLayout>

</LinearLayout>
3. Realiza una transacción de inserción de fragmento en AddEditLawyerActivity recibiendo el
identificador del abogado a través del intent entrante.

AddEditLawyerActivity.java

public class AddEditLawyerActivity extends AppCompatActivity {

public static final int REQUEST_ADD_LAWYER = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_edit);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

String lawyerId = getIntent().getStringExtra(LawyersActivity.EXTRA_LAWYER_ID);

setTitle(lawyerId == null ? "Añadir abogado" : "Editar abogado");

AddEditLawyerFragment addEditLawyerFragment = (AddEditLawyerFragment)


getSupportFragmentManager().findFragmentById(R.id.add_edit_lawyer_containe
r);
if (addEditLawyerFragment == null) {
addEditLawyerFragment = AddEditLawyerFragment.newInstance(lawyerId);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.add_edit_lawyer_container, addEditLawyerFragment)
.commit();
}
}

@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
Guardar/Modificar abogado

El punto de interacción en el fab button obedece al siguiente flujo:

1.Usuario modifica campos de texto con datos de abogado


2.Usuario pulsa botón de guardado
3.Se inicia tarea asíncrona
1.Actualización: Ejecutar método update()

2.Inserción: Ejecutar método insert()

4.Se muestra la lista de abogados con la modificación


Con esto en mente veamos cómo proceder…

1. Obtén la instancia del fab en onCreateView() y asignale una escucha OnClickListener.


// Eventos

mSaveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
addEditLawyer();
}
});
Crea una tarea asíncrona que compruebe en doInBackground() el contenido de mLawyerId. Si
este no es null, entonces realizar una actualización, de lo contrario una inserción.

private class AddEditLawyerTask extends AsyncTask<Lawyer, Void, Boolean> {

@Override
protected Boolean doInBackground(Lawyer... lawyers) {
if (mLawyerId != null) {
return mLawyersDbHelper.updateLawyer(lawyers[0], mLawyerId) > 0;

} else {
return mLawyersDbHelper.saveLawyer(lawyers[0]) > 0;
}

@Override
protected void onPostExecute(Boolean result) {
showLawyersScreen(result);
}

}
Muestra la actividad de abogados en onPostExecute() con un nuevo método
llamado showLawyersScreen(). Setea el resultado de la actividad dependiendo del
comportamiento de la tarea asíncrona:
private void showLawyersScreen(Boolean requery) {

if (!requery) {
showAddEditError();
getActivity().setResult(Activity.RESULT_CANCELED);
} else {
getActivity().setResult(Activity.RESULT_OK);
}

getActivity().finish();
}

private void showAddEditError() {


Toast.makeText(getActivity(),
"Error al agregar nueva información", Toast.LENGTH_SHORT).show();
}
Crea un nuevo método llamado addEditLawyer(). En él debes extraer primero los datos de los
campos de texto, comprobar que no estén vacío y luego crea una nueva instancia Lawyer con
ellos. Finalmente inicia la tarea con este objeto.

private void addEditLawyer() {

boolean error = false;

String name = mNameField.getText().toString();


String phoneNumber = mPhoneNumberField.getText().toString();
String specialty = mSpecialtyField.getText().toString();
String bio = mBioField.getText().toString();

if (TextUtils.isEmpty(name)) {
mNameLabel.setError(getString(R.string.field_error));
error = true;
}

if (TextUtils.isEmpty(phoneNumber)) {
mPhoneNumberLabel.setError(getString(R.string.field_error));
error = true;
}

if (TextUtils.isEmpty(specialty)) {
mSpecialtyLabel.setError(getString(R.string.field_error));
error = true;
}

if (TextUtils.isEmpty(bio)) {
mBioLabel.setError(getString(R.string.field_error));
error = true;
}

if (error) {
return;
}

Lawyer lawyer = new Lawyer(name, specialty, phoneNumber, bio, "");

new AddEditLawyerTask().execute(lawyer);

}
niciar actividad de creación

Ve a LawyersFragment, obtén la referencia del fab para agregar y setea una


escucha OnClickListenener.

mAddButton.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View view) {
showAddScreen();
}
});
Crea un nuevo método llamado showAddScreen(). Dentro de este inicia a AddEditLawyerActivity.
Luego llamalo dentro del controlador onClick().

Centres d'intérêt liés