Préstamos de libros: Pantalla principal

Nuestro objetivo

Desarrollar una aplicación que permita hacer un seguimiento de los libros que prestamos a nuestros contactos. La pantalla principal debería mostrar la lista de préstamos. 

El dominio

Nuestro modelo es bastante sencillo:

Suele ocurrir que la complejidad de las aplicaciones móviles está más en la interfaz de usuario que en el dominio, puede que esto tenga que ver con la naturaleza de un lugar donde todo está por construir y además por la dificultad que todavía representa desarrollar en este tipo de plataformas.

Decisiones a tomar

  • los contactos ¿salen del dispositivo o tendremos una entidad aparte? Lo más razonable es que el objeto Contacto esté asociado a los que cargamos en nuestro dispositivo, pero esta es una decisión que hay que consensuar con el usuario.
  • ¿dónde guardamos los préstamos? ¿necesitaremos identificadores unívocos?
  • ¿cómo navegar la aplicación?

Desarrollo iterativo

Para resolver la primera pantalla sólo necesitamos pensar algunas de estas cosas, las demás quedarán para más adelante. 

Repositorio

La home es una interfaz, con una implementación básica que trabaja con una colección en memoria, que se genera al comenzar la aplicación. Algunas consideraciones:
  • Más adelante podemos pensar en persistir la información del dispositivo en una base de datos local SQLite. Pero atacar de entrada la persistencia nos distrae de otros objetivos que queremos lograr por el momento.
  • La clase CollectionBasedPrestamos es un Singleton, 
    • eso nos facilita el acceso desde cualquier actividad
    • ¿complica el testeo unitario? sí, sabemos que reemplazar CollectionBasedPrestamos.instance por otro objeto mockeado tiene un costo. De todas maneras los únicos que van a acceder al home son las actividades (que no pueden testearse en forma automatizada con las herramientas tradicionales), así que podemos relajar este punto.
  • El método que ofrece el Home para la MainActivity es getPrestamosPendientes():
    @Override
    public List<Prestamo> getPrestamosPendientes() {
        // Hay que configurar el proyecto para que utilice Jackson
        return this.prestamos
                .stream()
                .filter((Prestamo prestamo) -> prestamo.estaPendiente())
                .collect(Collectors.toList());

    }


  • Para poder activar código en Java 8 que luego se pase a la SDK de Android necesitamos incorporar en el build.gradle la librería Jack:


android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    defaultConfig {
        ...
        jackOptions {
            enabled true
        }
    }

Vista

La primera versión de nuestra pantalla es simple}
  • Layout lineal
    • ListView de préstamos como un content

En res/layout/activity_main.xml:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/TableLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/lvPrestamos"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:choiceMode="singleChoice"
        ></ListView>

</LinearLayout>

Layout

Veamos las diferencias al definir el layout en el ancho (width) y alto (height):
  • para el ancho del textview que muestra la información de un libro consideraremos el tamaño de la pantalla: match_parent
  • para el alto, nos interesa que aparezca toda la información del libro sin truncar, por eso usamos wrap_content

Controller

Juego de datos

Hay que generar los préstamos, esto incluye crear objetos contactos y libros. Esto lo hacemos en un método initialize() propio de una clase
PrestamosAppBootstrap:


public class PrestamosAppBootstrap {

    public static void initialize(MainActivity activity) {
        /**
         * inicializamos la información de la aplicación
         */
        RepoContactos repoContactos = new PhoneBasedContactos(activity);
        repoContactos.addContactoSiNoExiste(
                new Contacto("1", "46425829", "Chiara Dodino", "kiki.dodain@gmail.com", ImageUtil.convertToImage(activity, "kiarush.png")));
        repoContactos.addContactoSiNoExiste(
                new Contacto("2", "45387743", "Ornella Bordino", "ornelia@yahoo.com.ar", ImageUtil.convertToImage(activity, "ornella.jpg")));
        ...

        Libro elAleph = new Libro(1, "El Aleph", "J.L. Borges");
        elAleph.prestar();
        Libro laNovelaDePeron = new Libro(2, "La novela de Perón", "T.E. Martínez");
        laNovelaDePeron.prestar();
        ...

        RepoLibros repoLibros = PrestamosConfig.getRepoLibros(activity);

        // Cuando necesitemos generar una lista nueva de libros
        // homeDeLibros.eliminarLibros()
        elAleph = repoLibros.addLibroSiNoExiste(elAleph);
        laNovelaDePeron = repoLibros.addLibroSiNoExiste(laNovelaDePeron);
        cartasMarcadas = repoLibros.addLibroSiNoExiste(cartasMarcadas);
        repoLibros.addLibroSiNoExiste(new Libro(4, "Rayuela", "J. Cortázar"));
        ...

        Contacto gise = new Contacto(null, "46050144", null, null, null);
        Contacto fede = new Contacto(null, "47067261", null, null, null);
        ...

        RepoPrestamos repoPrestamos = PrestamosConfig.getRepoPrestamos(activity);
        if (repoPrestamos.getPrestamosPendientes().isEmpty()) {
            Log.w("Librex", "Creando préstamos");
            repoPrestamos.addPrestamo(new Prestamo(1L, repoContactos.getContacto(fede), elAleph));
            repoPrestamos.addPrestamo(new Prestamo(2L, repoContactos.getContacto(gise), laNovelaDePeron));
            repoPrestamos.addPrestamo(new Prestamo(3L, repoContactos.getContacto(orne), cartasMarcadas));
        }
    }
}


Después explicaremos en detalle cómo se cargan los contactos.

Adapter entre ListView y la lista de préstamos

Redefinimos el evento de creación de la actividad invocando al initialize():


  @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestContactPermission();
        super.onCreate(savedInstanceState);
        PrestamosAppBootstrap.initialize(this);
        setContentView(R.layout.activity_main);
        ListView lvPrestamos = (ListView) findViewById(R.id.lvPrestamos);


           

Para llenar los préstamos pendientes, inicialmente podemos llamar a this.llenarPrestamosPendientes(); aunque más adelante tendremos que gestionar los permisos especiales para poder
  • enviar mail a un contacto
  • o llamarlo
  • o leer un contacto
Pero eso lo veremos más adelante. Lo que vamos a hacer aquí es definir un ArrayAdapter:

    private void llenarPrestamosPendientes() {
        ListView lvPrestamos = (ListView) findViewById(R.id.lvPrestamos);
        PrestamoAdapter prestamoAdapter = new PrestamoAdapter(this, repoPrestamos.getPrestamosPendientes());
        lvPrestamos.setAdapter(prestamoAdapter);
    }


Esto permite asociar la lista de elementos de la ListView con un conjunto de datos:


El layout android.R.layout.simple_list_item_1 sirve si solamente queremos mostrar una línea por cada elemento de la ListView. Necesitamos redefinir el toString() default de Préstamo y Libro, claro está, para que no se vea el "Libro@3725dfa" default en el dispositivo.

Para Préstamo:

    @Override
    public String toString() {
        return libro.toString() + " - " + this.getDatosPrestamo();
    }

Para Libro:


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