App Abogado
App Abogado
App Abogado
Con esto listo, procedamos a los pasos para implementar nuestra SQLite App.
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;
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 {
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:
/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.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// No hay operaciones
}
Recibe tres parámetros:
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.
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í:
@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);
return sqLiteDatabase.insert(
LawyerEntry.TABLE_NAME,
null,
lawyer.toContentValues());
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.
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:
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.
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…
activity_lawyers.xml
<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>
<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
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
}
@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().
<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>
<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);
if (fragment == null) {
fragment = LawyersFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.lawyers_container, fragment)
.commit();
}
}
}
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);
}
@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.
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:
mLawyersList.setAdapter(mLawyersAdapter);
public LawyersFragment() {
// Required empty public constructor
}
@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;
}
@Override
protected void onPostExecute(Cursor cursor) {
if (cursor != null && cursor.getCount() > 0) {
mLawyersAdapter.swapCursor(cursor);
} else {
// Mostrar empty state
}
}
}
if(fragment==null){
fragment = LawyersFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.lawyers_container, fragment)
.commit();
}
}
}
En ella se proyectarán el resto de datos del abogado para mostrar la entidad completa.
IMAGEN PENDIENTE
<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">
<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>
</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
</android.support.v4.widget.NestedScrollView>
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 {
public LawyerDetailFragment() {
// Required empty public constructor
}
@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);
return root;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Acciones
}
Basado en esa idea, intenta recrear el siguiente mock con la organización que desees:
IMAGEN PENDIENTE
<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);
@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...
loadLawyer();
return root;
}
@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));
}
showDetailScreen(currentLawyerId);
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
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
IMAGEN PENDIENTE
Ya que hemos comprobado que existe nuestra base de datos, iniciaremos sqlite3 dentro del
dispositivo.
IMAGEN PENDIENTE
<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:/ #
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:
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
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…
db.delete(
LawyerEntry.TABLE_NAME,
selection,
selectionArgs);
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>
@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().
@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.
Nombre de la tabla
Valores nuevos
Instrucción WHERE
Argumentos del WHERE
Por ejemplo…
// Valores
ContentValues values = new ContentValues();
// 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}
);
}
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.
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>
<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>
content_add_edit.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/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
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.
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";
public AddEditLawyerFragment() {
// Required empty public constructor
}
@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();
}
});
// Carga de datos
if (mLawyerId != null) {
loadLawyer();
}
return root;
}
}
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.
IMAGEN PENDIENTE
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
@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);
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
Guardar/Modificar abogado
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.
@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();
}
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;
}
new AddEditLawyerTask().execute(lawyer);
}
niciar actividad de creación
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().