Préstamos de libros: Crear nuevo préstamo

Nuestro objetivo

Definir la actividad que permita cargar un nuevo préstamo.

Pasos

  • seleccionar el libro que uno está prestando, mediante una búsqueda rápida que muestra las opciones a medida que va escribiendo
  • seleccionar el contacto 
  • la fecha se asume la del día de hoy
    • BONUS: que sea posible cambiarla mediante un control calendario
  • agregar el préstamo en la lista y cerrar la actividad

Persistencia

Toda esta información
  1. queda en memoria mientras dura la aplicación o
  2. se persiste en un SQL Lite, teniendo en cuenta 
    1. la relación many-to-one entre las entidades Préstamo y Libro. En la tabla Préstamo se almacena el identificador unívoco de Libro (LIBRO_ID según vimos)
    2. la relación many-to-one contra los contactos, que se guardan en el dispositivo. La referencia al contacto puede ser el ID (que podría re-mapearse al RAW_CONTACT_ID), el teléfono (PHONE), o bien el nombre del contacto. En el ejemplo elegimos la segunda opción para no tener que hacer tantos cambios en la aplicación. 
Vista
Definimos un LinearLayout vertical, donde
  • en la primera línea utilizamos un control AutoCompleteTextView txtLibroTituloAutocomplete, le incorporamos la propiedad hint para dar una idea al usuario de lo que tiene que seleccionar
  • para mostrar los datos del contacto en una sola línea, generamos un LinearLayout horizontal. Es importante que el ancho y alto se configuren wrap_content, para que traten de ocupar el máximo ancho posible de la línea, pero dejen espacio al botón que está abajo. En esta línea se ubican
    • un ImageView imgContacto que muestre la foto del contacto
    • un TextView txtContacto  que muestre la información del contacto: esto puede ser sólo nombre o podría incluir algún dato de contacto
    • un ImageButton btnBuscar que dispare la app búsqueda de contactos del dispositivo
    • si no tenemos un contacto seleccionado, la imagen y el texto se ocultan
  • un Button btnPrestar que dispare la acción que crear el nuevo préstamo, en el atributo onClick = prestar.

Controller

Búsqueda con Autocomplete

En el onCreateOptionsMenu de la actividad le pasamos al control autocomplete la lista de libros pendientes:

        List<Libro> libros = PrestamosConfig.getRepoLibros(this).getLibrosPrestables();
        for (Libro libro: libros) {
            mapaLibros.put(libro.toString(), libro);
        }
        txtLibroAutocomplete = (AutoCompleteTextView) findViewById(R.id.txtLibroTituloAutocomplete);
        txtLibroAutocomplete.setAdapter(new ArrayAdapter<Libro>(this, 
            android.R.layout.simple_dropdown_item_1line, libros));
        txtLibroAutocomplete.addTextChangedListener(this);

Como el toString() de Libro concatena título y autor

    @Override
    public String toString() {
        return titulo + " (" + autor + ")";
    }

la estrategia que adoptamos es crear un mapa cuya clave será el toString de libro y cuyo valor es el objeto Libro (una estrategia un tanto heterodoxa porque no manejamos objetos seleccionados como cuando disponemos de binding).

Como la Activity implementa TextWatcher podemos capturar el evento onTextChanged para hacer la búsqueda en el mapa de libros en base al valor ingresado:

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        libroSeleccionado = mapaLibros.get(txtLibroAutocomplete.getText().toString());
    }

Si escribimos algo que no está en el mapa la variable libroSeleccionado quedará en null.

Búsqueda de contactos

Al hacer click en el botón se dispara un intent a la aplicación de búsqueda de contactos que trae el dispositivo

    public void buscarContacto(View view) {
        Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
        startActivityForResult(intent, PICK_CONTACT);
    }

Una vez que seleccionamos un contacto, hay que capturar el evento como callback:

    public void onActivityResult(int reqCode, int resultCode, Intent data) {
        switch (reqCode) {
            case (PICK_CONTACT) :
                if (resultCode == Activity.RESULT_OK) {
                    seleccionarContacto(data);
                }
        }
    }

    private void seleccionarContacto(Intent data) {
        CursorLoader loader = new CursorLoader(this, data.getData(), null, null, null, null);
        Cursor cursor = loader.loadInBackground();
        if (cursor.moveToFirst()) {
            String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
            Contacto contactoBuscar = new Contacto();
            contactoBuscar.setNombre(name);
            // http://developer.android.com/reference/android/os/StrictMode.html
            contacto = PrestamosConfig.getRepoContactos(this).getContacto(contactoBuscar);
            TextView txtContacto = (TextView) findViewById(R.id.txtContacto);
            txtContacto.setText(contacto.getNombre());
            ImageView imgContacto = (ImageView) findViewById(R.id.imgContactoAPrestar);
            ImageUtil.assignImage(contacto, imgContacto);
        }
    }

En el método seleccionarContacto recibimos un cursor, con el que generamos contactoBuscar, el objeto Contacto que nos sirve de prototipo para hacer la búsqueda al home de Contactos que ya diseñamos. La ventaja es que el home nos devuelve un Contacto ya construido, solamente tenemos que encargarnos del binding de la imagen con la foto y el textview con los datos del contacto.

Prestar

Al prestar hay que instanciar un préstamo con 
  • el libro seleccionado, que lo tenemos en una variable de instancia libroSeleccionado (se carga en el método onTextChanged del autocomplete)
  • el contacto seleccionado, que lo tenemos también en una variable local contacto (se carga en el método seleccionarContacto)
luego delegar al repositorio y cerrar la actividad. Si hay errores de validación, se muestra un Toast al usuario y se corta el flujo (la actividad no se cierra ni se agrega el préstamo). Si hay un error diferente (de programación), el mensaje es diferente

    public void prestar(View view) {
        try {
            Prestamo prestamo = new Prestamo();
            prestamo.setLibro(libroSeleccionado);
            prestamo.setContacto(contacto);
            prestamo.prestar();
            getRepoPrestamos().addPrestamo(prestamo);
            getRepoLibros().updateLibro(libroSeleccionado);
            this.finish();
        } catch (BusinessException be) {
            Toast.makeText(this, be.getMessage(), Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            Log.e("Crear prestamo", e.getMessage());
            e.printStackTrace();
            Toast.makeText(this, "Ocurrió un error. Consulte con el administrador del sistema.", Toast.LENGTH_SHORT).show();
        }
    }

Por último hay que indicar en el MainActivity que refresque la lista de préstamos al reactivarse la actividad:

    @Override
    protected void onResume() {
        super.onResume();
        this.llenarPrestamosPendientes();
    }


Diagrama de clases de la solución


Ejecutando la aplicación

Vemos cómo queda el resultado final.




.