Poblar Spinners En Android Desde Una Base De Datos SQLite

Los Spinners son menús desplegables en Android que permiten al usuario decidirse por un camino entre varias opciones.

De seguro alguna vez habrás visto un formulario en una aplicación donde te pidan elegir tu ciudad de una lista o cuando te permiten cambiar la temporalidad de los resultados proyectados desde un menú en la Action Bar.

Ambos casos son ejemplos de Spinners y espero que este tutorial te sirva de guía para poder usarlos. Sigue leyendo y podrás descargar un ejemplo completo para interiorizar los conocimientos.

¿Qué es un Spinner?

Un Spinner es un View que despliega una lista de elementos que están expectantes a la selección del usuario. Dependiendo del elemento seleccionado nuestra aplicación tomará las decisiones pertinentes para seguir el flujo de ejecución.
Ejemplo de Spinner en Contactos de Android
El concepto es sencillo y he preparado un pequeño ejemplo para que puedas interiorizarlo. Se trata de una pequeña aplicación llamada Gamerty, que permite seleccionar la guía del videojuego que elija el usuario del Spinner. Veamos como se ve:
Aplicación Android con un Spinner
Para desbloquear el link de descarga del código completo,  sigue estas instrucciones:

[sociallocker id=»7121″]Descargar Gratis[/sociallocker]

Ahora procedamos a recrear su elaboración…

Ubicar un Spinner en Android Studio

El primero paso es la creación de un nuevo proyecto en Android Studio con una actividad principal, a la cual le llamarás Main. Inmediatamente vas a su archivo de diseño (activity_main.xml) y añades desde la ventana Pallete el Spinner.

Le asignarás el identificador «GameSpinner», lo centras horizontalmente (centerHorizontal) y luego lo haces limitar con el borde superior del padre (alignParentTop).
Añadir Spinner en Android Studio
Si todo te ha salido excelente, tendrías el siguiente archivo de diseño:

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".Main">

    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/GameSpinner"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

Crear un Origen de Datos

Al igual que cuando vimos <?xml version="1.0" encoding="utf-8"?> <resources> <!-- Strings generales --> <string name="app_name">Gamerty</string> <!-- Conjunto de Strings para el View GameSpinner --> <string-array name="Games"> <item>League of Legends</item> <item>Diablo III</item> <item>God of War</item> <item>Halo 4</item> <item>StarCraft II</item> </string-array> </resources>

Si te fijas en la definición, verás un elemento <string-array> con nombre «Games». Si aún no lo sabes, este elemento representa un array de strings (como su nombre lo indica). Dentro de él debes incluir cada dato como un nodo <item>, cuyo contenido es la cadena.

Poblar un Spinner con un Adaptador

Una vez hayas definido tu origen de datos vas a crear un adaptador que se ajuste a tus necesidades. En el caso de Gamerty se usa un ArrayAdapter con elementos simples, ya que solo necesitas proyectar el nombre del juego.

Para ello se obtiene la instancia del Spinner, luego declaras el adaptador, lo inicializas en el método onCreate() de la actividad Main y finalmente los relacionas:

//ArrayAdapter para conectar el Spinner a nuestros recursos strings.xml
protected ArrayAdapter<CharSequence> adapter;
...
//Obtener instancia del GameSpinner
Spinner spinner = (Spinner) findViewById(R.id.GameSpinner);

//Asignas el origen de datos desde los recursos
adapter = ArrayAdapter.createFromResource(this, R.array.Games,
                android.R.layout.simple_spinner_item);

//Asignas el layout a inflar para cada elemento
//al momento de desplegar la lista
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

//Seteas el adaptador 
spinner.setAdapter(adapter);

El código anterior muestra el uso del método de clase createFromResource(), el cual nos permite crear un nuevo ArrayAdapter a partir de los recursos string que están en el proyecto.

El primer parámetro que recibe es el contexto, en este caso es la actividad actual, luego la referencia del <array-string> que declaraste y por último el layout para inflar cada ítem, donde se usa el recurso del sistema simple_spinner_item.xml, que representa un TextView sencillo.

Luego usamos el método del adaptador setDropDownViewResource(). Su objetivo es setear un layout para cada ítem del Spinner, para cuando se despliegue la lista.

Tomaremos el recurso de la plataforma simple_spinner_dropdown_item.xml, el cual representa un text view seleccionable o CheckedTextView.

Este tipo de text view hereda los comportamientos de uno normal, solo que tiene algunas características adicionales en su forma de iluminarse.

Si deseas establecer la posición inicial del Spinner usa el método setSelection() con un índice entero como parámetro. Si pones atención, en Gamerty creamos una constante llamada DEFAULT_POSITION a la cual se le asigno el entero 3 que corresponde a "Halo 4".

Poblar un Spinner con el Atributo entries

Debido a que los elementos de nuestro Spinner son estáticos, podemos utilizar un atributo muy útil del elemento <Spinner> llamado android:entries. En él puedes asignar el arreglo de strings que se ha predefinido en los recursos y automáticamente se inflarán implícitamente los elementos el Spinner sin necesidad de un adaptador.

Solo basta con que te dirijas a la pestaña de diseño y modifiques el atributo con el siguiente valor «@array/Games»:
Atributo entries de un Spinner
O ir a la definición XML y realizar la respectiva asignación:

<Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/GameSpinner"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        <strong>android:entries="@array/Games" </strong>/>

Manejo de eventos de un Spinner

En esta sección verás cómo definir el flujo de tu aplicación de acuerdo a la opción del Spinner que sea seleccionada por el usuario. Para cumplir ese objetivo se empleará la escucha OnSelectedItemListener.

El primer paso que debes realizar es implementar sobre tu actividad Main la escucha. Luego en el método onCreate() le asignas la referencia de la escucha a tu Spinner:

public class Main extends Activity implements OnItemSelectedListener {
...
 @Override
    protected void onCreate(Bundle savedInstanceState) {
   ...
   //Asignado la escucha
   spinner.setOnItemSelectedListener(this);
   ...
  }
...
}

A continuación se deben implementar los métodos onItemSelected() y onNothingSelected() relacionados con OnItemSelectedListener. El primero es llamado cuando has seleccionado un elemento del Spinner y el segundo cuando no tiene ítems seleccionados.

¿Cuál es la idea?, vas a sobrescribir onItemSelected() y añadirás las instrucciones necesarias para que visualice un Toast. Este debe mostrar el nombre del elemento que se ha seleccionado.

Veamos:

@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

 //Salvar la posición y valor del item actual
    this.position = position;
    selection = parent.getItemAtPosition(position).toString();

    //Mostramos la selección actual del Spinner
    Toast.makeText(this,"Selección actual: "+selection,Toast.LENGTH_SHORT).show();

}

La primera tarea que se ha realizado es guardar la posición y el valor del texto en dos variables privadas llamadas position y selection (Si revisas el proyecto, han sido declaradas en el inicio de Main).

El parámetro position del método onItemSelected() se refiere al índice del elemento seleccionado, por lo que te queda fácil obtener una instancia con getItemAtPosition(). Luego puedes consultar el valor de su atributo text con toString().

Finalmente creas un Toast prefabricado y muestras el contenido de la selección.

Poblar un Spinner desde una Base de Datos SQLite

En esta sección aprenderás a crear los ítems de un Spinner desde un cursor que contenga el resultado de una consulta en SQLite (¿Aún no sabes cómo diseñar una base de datos? Obtén mi ebook con 8 pasos para crear modelos de datos

El proceso es el mismo que con las listas. Como ya sabes, solo debes usar la clase SimpleCursorAdapter y todo estará resuelto.

Suena fácil pero es mejor aclararlo con un ejemplo. La siguiente aplicación que estudiarás a continuación se llama Lyrik. Esta aplicación muestra las letras de las canciones de un artista disponible, clasificados por género musical.
Aplicación Android con dos Spinners
Al igual que el primer ejemplo, usa la siguiente caja de descarga:

[sociallocker id=»7121″]Descargar Gratis[/sociallocker]

Su diseño se basa en una actividad principal (Main) con dos spinners (GenreSpinner y ArtistSpinner). El primero para desplegar los géneros disponibles y el segundo para proyectar los artistas que pertenecen a ese género.

Por lo que el spinner de los artistas depende del género, representando una cardinalidad uno a muchos (1:N) en la base de datos.
Modelo relacional para Géneros y Artistas
Teniendo en cuenta lo anterior, la base de datos tendría dos tablas: Géneros(Genres) y Artistas(Artists). Donde Artistas recibiría una llave foránea relacionada con la llave principal de Géneros.

Una vez definido todas las características de la base de datos se crea un script que automatice su creación en cualquier momento. El nombre de clase es DataBaseScript (Obsérvala en el proyecto).

Usar la clase SimpleCursorAdapter con un Spinner

Si detallas la clase Main del proyecto, verás que el primer paso fue inicializar un adaptador del tipo SimpleCursorAdapter de la siguiente forma:

//Creando Adaptador para GenreSpinner
genreSpinnerAdapter = new SimpleCursorAdapter(this,
        android.R.layout.simple_spinner_item,
        dataSource.getAllGenres(),
        new String[]{DataBaseScript.GenreColumns.NAME_GENRE},
        new int[]{android.R.id.text1},
        SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

El constructor ha recibido el layout prefabricado simple_spinner_item.xml y como fuente de datos se ha establecido el cursor que retorna el método getAllGenres() de la clase LyrikDataSource.

Como su nombre lo indica, este método obtiene todas las filas de la tabla Genres de la base de datos. Su definición no es más que una sentencia SELECT que selecciona todas las columnas y registros:

public Cursor getAllGenres(){
        //Seleccionamos todas las filas de la tabla Genres
        return database.rawQuery(
                "select * from " + DataBaseScript.GENRES_TABLE_NAME, null);
    }

Hasta aquí todo muy bien. Ahora veamos la estrategia para poblar el Spinner de artistas.

Crear una dependencia entre dos Spinners

¿Cómo hacer para que el segundo Spinner sea poblado al elegirse un ítem del primer Spinner?

La respuesta está en los eventos de selección. Bien sabemos que la interfaz OnItemSelectedListener es quién implementa el método onItemSelected() para la ejecución de actividades de selección, así que es allí donde debemos aplicar nuestra lógica.

Ahora pregúntate: ¿Como obtengo los nombres de los artistas que están relacionados con un género?

En esencia, esta es una duda que debemos resolver primero desde el modelo de datos, es decir, en la base de datos. Y la solución es sencilla, simplemente debes consultar aquellos artistas cuyo valor de su campo idGenre sea igual al asignado en tiempo real por el flujo del programa.

Por ejemplo…

La siguiente consulta obtiene el id y nombre de los artistas del genero con código 1001:

SELECT _id,name
FROM Artists
WHERE idGenre = 1001;

Así que generalizaremos esta sentencia con el siguiente método llamado getArtistsByGenre() – «Obtener artistas por genero»:

public Cursor getArtistsByGenre(String genreSelection) {

        //Argumentos del WHERE
     String selectionArgs[] = new String[]{genreSelection};


        String query =
                        "select "+DataBaseScript.ArtistColumns.ID_ARTIST+","+DataBaseScript.ArtistColumns.NAME_ARTIST+
                        " from "+DataBaseScript.ARTISTS_TABLE_NAME +
                        " where "+DataBaseScript.ArtistColumns.ID_GENRE+
                        "= ?";
        return database.rawQuery(query,selectionArgs);
    }

Si detallas bien, usamos el placeholder ‘?’ para reemplazarlo por genreSelection, que es el parámetro de entrada que representa el id del género requerido. Esto facilita tu trabajo para crear el adaptador del Spinner de artistas.

Finalmente implementamos el método onItemSelected(), donde obtienes el id del género seleccionado y luego inicias el adaptador:

@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    //Obteniendo el id del Spinner que recibió el evento
    int idSpinner = parent.getId();


    switch(idSpinner) {

        case R.id.GenreSpinner:
        //Obteniendo el id del género seleccionado
        Cursor c1 = (Cursor) parent.getItemAtPosition(position);
        String genreSelection = c1.getString(
                c1.getColumnIndex(DataBaseScript.GenreColumns.ID_GENRE));

        //Poblar el ArtistSpinner
        activeArtistSpinner(genreSelection);
            break;
...

Recuerda que si el adaptador maneja cursores el resultados obtenido por getItemAtPosition() debe ser casteado a Cursor. Seguidamente puedes obtener el valor de la fila deseado.

Para activar el Spinner de artistas de forma personalizada se creó el método activeArtistSpinner(). Su trabajo es crear el adaptador con el id de entrada a través del método getArtistsByGenre(). Luego lo asignas al Spinner y finalmente relacionas la escucha:

private void activeArtistSpinner(String genreSelection) {

        //Creando Adaptador para ArtistSpinner con el id del género
        artistSpinnerAdapter = new SimpleCursorAdapter(this,
                android.R.layout.simple_spinner_item,
                dataSource.getArtistsByGenre(genreSelection),
                new String[]{DataBaseScript.ArtistColumns.NAME_ARTIST},
                new int[]{android.R.id.text1},
                SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

        //Seteando el adaptador creado
        artistSpinner.setAdapter(artistSpinnerAdapter);

        //Relacionado la escucha
        artistSpinner.setOnItemSelectedListener(this);

    }

Ahora solo queda agregar al método onItemSelected() las acciones que se realizaran cuando sea seleccionado un elemento del ArtistSpinner:

case R.id.ArtistSpinner:
//Obteniendo el nombre del artista seleccionado
Cursor c2 = (Cursor) parent.getItemAtPosition(position);
String artistSelection = c2.getString(
        c2.getColumnIndex(DataBaseScript.ArtistColumns.NAME_ARTIST));

//Cambiando el texto de LyricList según el Artista seleccionado
lyricList.setText(getResources().getString(R.string.LyricList)+" "+artistSelection);

break;

La idea fue asignarle al TextView un mensaje del tipo «Letras de <Artista>», donde <Artista> será reemplazado por la selección actual del usuario.

Añadir un Spinner a la Action Bar

Si logras recordar un poco sobre la anatomía de la Action Bar tendrás presente que existe una sección llamada View Control. Se había dicho que en este espacio era posible inflar Views que interactuan con el contenido actual de la actividad o fragmento. Muy bien, en este apartado veremos cómo inflar un Spinner justo en ese lugar para implementar un filtro de datos.

Dicho filtro será incluido en una aplicación llamada Fazhion. Esta aplicación fue pedida por un cliente imaginario que desea proyectar en una actividad todos los estilos de zapatos que vende su negocio. Quiere que se muestre el nombre del zapato, su precio, la descripción y una miniatura de su aspecto. Además desea que el cliente pueda filtrar los productos por precio y nombre.

Así que lo que harás es añadir un Spinner a la Action Bar para solucionar este problema. Donde el resultado final será algo como esto:
Spinner en la Action Bar
De nuevo, pon tu correo para desbloquear el link de descarga de Fazhion:

[sociallocker id=»7121″]Descargar Gratis[/sociallocker]

La aplicación Fazhion se compone de una ListActivity principal llamada Main. Los elementos de la lista son inflados a través de un CursorAdapter personalizado que accede a la base de datos Fazhion previamente creada.

Ahora veamos como se hizo para añadir el Spinner a la Action Bar:

Paso 1: Crear el Adaptador del Spinner

Puedes usar un ArrayAdapter personalizado o un SimpleCursorAdapter como vimos con anterioridad. También puedes personalizar los ítems al igual que en la captura de la aplicación Fazhion, cuyo Spinner tiene un ImageView en cada ítem.

Si deseas añadir un ImageView al Spinner debes crear un layout personalizado. Fazhion utiliza el layout spinner_item.xml, cuyo diseño se proyecta en el siguiente bosquejo:
Layout de los Items de un Spinner
Y su descripción xml es la siguiente:

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="5dp">

    <ImageView
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:id="@+id/ItemIcon"
        android:src="@drawable/ic_launcher"
        android:layout_alignParentLeft="true"
        android:layout_centerInParent="true"
        android:layout_marginRight="3dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Texto"
        android:id="@+id/TextItem"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@+id/ItemIcon" />
</RelativeLayout>

Luego debes crear un adaptador personalizado que soporte el anterior layout. Esto implica crear un origen de datos basado en una clase que represente cada elemento del Spinner.

Si abstraes las características de cada elemento del Spinner, deducirás que poseen un icono que proyecta su significado y un nombre. Así que crea una nueva clase con estas características y llámala SpinnerItem:

public class SpinnerItem {
    private String Name;
    private int iconId;

    public SpinnerItem(String name, int iconId) {
        Name = name;
        this.iconId = iconId;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public int getIconId() {
        return iconId;
    }

    public void setIconId(int iconId) {
        this.iconId = iconId;
    }
}

Por último debes crear un adaptador que infle cada view a través de una lista de tipo SpinnerItem. A este le denominarás FilterAdapter:

public class FilterAdapter extends ArrayAdapter {

    public FilterAdapter(Context context, List<SpinnerItem> objects) {
        super(context, 0, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {


       /*
       Obtener el objeto procesado actualmente
        */
        SpinnerItem currentItem = getItem(position);

        /*
        Obtener LayoutInflater de la actividad
         */
        LayoutInflater inflater = (LayoutInflater) parent.getContext().
                getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        /*
        Evitar inflar de nuevo un elemento previamente inflado
         */
        if(convertView==null){
            convertView = inflater.inflate(R.layout.spinner_item, parent, false);
        }

        /*
        Instancias del Texto y el Icono
         */
        TextView name = (TextView)convertView.findViewById(R.id.TextItem);
        ImageView icon = (ImageView)convertView.findViewById(R.id.ItemIcon);

        /*
        Asignar valores
         */
        name.setText(currentItem.getName());
        icon.setImageResource(currentItem.getIconId());

        return convertView;
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        /*
        Debido a que deseamos usar spinner_item.xml para inflar los
        items del Spinner en ambos casos, entonces llamamos a getView()
         */
        return getView(position,convertView,parent);
    }
}

Si eres buen observador verás algo inusual que nunca antes habíamos hecho al definir un adaptador. Hemos sobrescrito un método llamado getDropDownnView().

Este método tiene las mismas funciones de getView(), solo que se ejecuta cuando el Spinner es desplegado. Como ya habías visto, los ítems del Spinner pueden utilizar otro diseño en ese momento.

Debido a que no deseamos que tomen otra forma, simplemente retornamos en el mismo view que genera el método getView().

Ahora solo crea en tu actividad principal una lista de ítems para el Spinner y luego asignalos en el constructor del nuevo FilterAdapter:

/*
Inicializar los items estáticamente
 */
List items = new ArrayList();
items.add(new SpinnerItem("Mayor Precio",R.drawable.ic_higher_prices));
items.add(new SpinnerItem("Menor Precio",R.drawable.ic_lower_prices));
items.add(new SpinnerItem("Alfabéticamente",R.drawable.ic_alphabetical_order));

//Creando el nuevo adaptador para el Spinner
spinnerAdapter = new FilterAdapter(this,items);

Paso 2: Implementar la interfaz OnNavigationListener

Para los eventos usaremos las clases OnNavigationListener. Esta interfaz te permite decidir qué sucederá cuando el usuario selecciona uno de los elementos del Spinner en la Action Bar. En el caso de Fazhion se ejecutaron las consultas necesarias en la base de datos para obtener los registros de forma ordenada:

OnNavigationListener navigationListener =new OnNavigationListener() {
        /*
        0 = Mayor Precio
        1 = Menor Precio
        2 = Alfabeticamente
         */
        @Override
        public boolean onNavigationItemSelected(int position, long itemId) {

            switch(position){
                case 0:
                    /*
                    Obtener precios en orden descendente
                    */
                    fazhionAdapter.changeCursor(dataSource.getShoesOrderPrice(false));
                    break;
                case 1:
                   /*
                    Obtener precios en orden ascendente
                     */
                    fazhionAdapter.changeCursor(dataSource.getShoesOrderPrice(true));
                    break;
                case 2:
                   /*
                    Obtener nombres en orden alfabético
                     */
                    fazhionAdapter.changeCursor(dataSource.getShoesOrderAZ(true));
                    break;
            }
            return true;
        }
    };

La escucha OnNavigationLisner te entrega a disposición el método onNavigationItemSelected() para definir las acciones de cada elemento del Spinner. Donde el parámetro entero position será nuestro referente para identificar cada elemento.

Al seleccionar el ítem en la posición 0, el adaptador de la lista actualiza el cursor a través de un método llamado getShoesOrderPrice(). Este ordena por precio la lista de los zapatos.

Será en orden descendente si recibe false y en orden ascendente si recibe true. En este caso consulta los registros de forma descendente y para la posición 1 se hace en forma ascendente.

El elemento en la posición 2 llama a getShoesOrderAZ(). Este método ordena alfabéticamente los nombres de los zapatos. Si recibe true lo hace de A-Z, si es false ordena de Z-A.

Revisa la clase FazhionDataSource del proyecto. Allí encontrarás la definición de ambos métodos.

La navegación de lista le indica a la action bar que inflaremos un menú con una lista de ítems, para gestionar la navegación entre fragmentos o actividades. Pero en el caso de Fazhion no habrá navegación, si no que filtraremos los elementos de la ListActivity.

Si deseas activar este modo y relacionar la escucha haz lo siguiente:

//Obteniendo una instancia de la Action bar
ActionBar actionBar = getActionBar();

if( actionBar != null) {

    //Habilitiando el modo de navegación con lista
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

    //Seteando la escucha y el spinner relacionado
    actionBar.setListNavigationCallbacks(spinnerAdapter, navigationListener);
}

El código anterior muestra como se activa el modo de navegación por lista con el método setNavigationMode(). Este recibe como parámetro una constante llamada NAVIGATION_MODE_LIST para que el Spinner sea funcional sobre la Action Bar.

Luego debes asignar la escucha y el adaptador del Spinner con el método setListNavigationCallbacks(). Con ello tendrás habilitado el modo de navegación y la funcionalidad DropDown del Spinner.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!