Añadiendo preferencias de usuario

Android nos facilita la configuración de nuestros programas, al permitir añadir una lista de preferencias que el usuario podrá modificar. Por ejemplo, el usuario podrá indicar con que frecuencia la aplicación ha de sincronizarse con el servidor o si queremos que se lancen notificaciones. Las preferencias también pueden utilizarse para que tu aplicación almacene información de forma permanente. En el capítulo 9 se estudiará cómo realizar esta función.
 

video[TutorialAñadir preferencias en Android

NOTA: A continuación se proponen una serie de ejercicios para añadir preferencias en Mis Lugares.  Para hacerlo en Asteroides puedes cambiarlos textos que aparecen, para que correspondan con la siguiente captura:

Ejercicio paso a paso: Añadiendo preferencias en la aplicación.


1.    Abre el proyecto Mis Lugares (o Asteroides)..

2.    Pulsa con el botón derecho sobre res y selecciona la opción New > Android resource file.

3.   Completa los campos File name: preferencias y Resource type: XML. Se creará el fichero res/xml/preferencias.xml.

4.    Edita este fichero. Selecciona la lengüeta Code  e introduce el siguiente código:

<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <SwitchPreferenceCompat
        android:key="notificaciones"
        android:title="Mandar notificaciones"
        android:summary="Notificar si estamos cerca de un lugar"/>
    <EditTextPreference
        android:key="maximo"
        android:title="Máximo de lugares a mostrar"
        android:summary="Limita en número de valores que se muestran"
        android:inputType="number"
        android:defaultValue="12"/>
    <ListPreference
        android:key="orden"
        android:title="Criterio de ordenación"
        android:summary="Que lugares quieres que aparezcan antes"
        android:entries="@array/tiposOrden"
        android:entryValues="@array/tiposOrdenValores"
        android:defaultValue="0"/>
</androidx.preference.PreferenceScreen>

El significado de cada etiqueta y atributo se descubre fácilmente si observas el resultado obtenido que se muestra a continuación. El atributo inputType permite configurar el tipo de teclado que se mostrará para introducir el valor. Coinciden con el atributo de EditText. Para ver los posibles valores consultar: developer.android.com/reference/android/widget/TextView.html#attr_android:inputType
 

5.   Para almacenar los valores del desplegable, has de crear el fichero /res/values/arrays.xml con el siguiente contenido:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="tiposOrden">
        <item>vectorial</item>
        <item>bitmap</item>
        <item>3D</item>
    </string-array>
    <string-array name="tiposOrdenValores">
        <item>0</item>
        <item>1</item>
        <item>2</item>
    </string-array>
</resources> 

6.  Añade al fichero Gradle Scripts/Bulid.gradle(Module:app) la dependencia:


dependencies {
    …
    implementation 'androidx.preference:preference:1.1.1'
} 
7.     Crea una nueva clase PreferenciasFragment con el siguiente código:

public class PreferenciasFragment extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle savedInstanceState,
                                    String rootKey) {
        setPreferencesFromResource(R.xml.preferencias, rootKey);
    }
}
class PreferenciasFragment : PreferenceFragmentCompat() {
   override fun onCreatePreferences(savedInstanceState: Bundle?,
                                    rootKey: String?) {
      setPreferencesFromResource(R.xml.preferencias, rootKey)
   }
} 

Nota sobre Java/Kotlin: Pulsa Alt-Intro para que automáticamente se añadan los paquetes que faltan.

La clase PreferenceFragmentCompat permite crear un fragment que contiene una ventana con las opciones de preferencias definidas en un recurso XML. Un fragment es un elemento que puede ser incrustado dentro de una actividad. El uso de fragment se estudia con más detalle en la última unidad del curso.

8.     Ahora vamos a crear una actividad que simplemente muestre el fragment anterior. Crea la la clase PreferenciasActivity con el siguiente código:

public class PreferenciasActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportFragmentManager().beginTransaction()
                .replace(android.R.id.content, new PreferenciasFragment())
                .commit();
    }
} 
class PreferenciasActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      supportFragmentManager.beginTransaction()
         .replace(android.R.id.content, PreferenciasFragment())
         .commit()
   }
} 

Desde una actividad podemos visualizar un fragment en tiempo de ejecución. Para ello utilizamos el manejador de fragments de la actividad (getSupportFragmentManager()) y comenzamos una transacción (beginTransaction()). Una transacción es una operación de insertado, borrado o reemplazo de fragments. En el ejemplo vamos a reemplazar el contenido de la actividad por un nuevo fragment de la clase PreferenciasFragment. Finalmente se llama a commit() para que se ejecute la transacción.

9.     No hay que olvidar registrar toda nueva actividad en AndroidManifest.xml.

10.     Añade a MainActivity.java el método lanzarPreferencias(). Este método ha de tener el mismo código que lanzarAcercaDe() pero lanzando la actividad PreferenciasActivity. En el layout activity_main.xml añade al botón con el texto «Configurar» en el atributo onClick el valor lanzarPreferencias.

11.   Para activar la configuración desde la opción de menú añade el siguiente código en el fichero MainActivity.java en el método onOptionsItemSelected().

if  (id == R.id.action_settings) {
   lanzarPreferencias(null);
   return true;
} 
R.id.action_settings -> {
   lanzarPreferencias()
   true
} 

Si has hecho la práctica “Casos de Uso para arrancar actividades” utiliza mejor el código usoActividades.lanzarPreferencias() para lanzar la actividad.

12.     Arranca la aplicación y verifica que puedes lanzar las preferencias mediante las dos alternativas.

NOTA: Si introduces un valor en el campo donde se ha indicado inputType="number", comprobarás que no funciona correctamente. Se trata de un bug de Google, todavía sin resolver. De momento nos recomiendan que usemos la siguiente librería https://github.com/Gericop/Android-Support-Preference-V7-Fix. Sigue los dos sencillos pasos indicados en README.md. 
 

Organizando preferencias

Cuando el número de preferencias es grande, resulta interesante organizarlas de forma adecuada. Una posibilidad consiste en dividirlas en varias pantallas, de forma que cuando se seleccione una opción en la primera pantalla, se abra una nueva pantalla de preferencias. Para organizar las preferencias de esta forma, usa el siguiente esquema:

<PreferenceScreen>
  <CheckBoxPreference …/>
  <EditTextPreference …/>
  …
  <PreferenceScreen android:title=”Modo multijugador”>
    <CheckBoxPreference …/>
    <EditTextPreference …/>
    …
  </PreferenceScreen>
</PreferenceScreen> 

Práctica: Organizando preferencias(I)

1. Crea una nueva lista de preferencias <PreferenceScreen> dentro de la lista de preferencias del fichero res/xml/preferencias.xml.

2. Asígnale al parámetro android:title el valor “Notificaciones por correo”.

3. Crea tres elementos dentro dentro de esta lista: Activar multijugador, Máximo de jugadores y Tipo de conexión. Para este último, han de poder escogerse los valores: Bluetooth, Wi-Fi e Internet.

Otra alternativa para organizar las preferencias consiste en agruparlas por categorías. Con esta opción se visualizarán en la misma pantalla, pero separadas por grupos. Has de seguir el siguiente esquema:

<PreferenceScreen>
  <CheckBoxPreference …/>
  <EditTextPreference …/>
  …
  <PreferenceCategory android:title=”Modo multijugador”>
    <CheckBoxPreference …/>
    <EditTextPreference …/>
.  . …
  </PreferenceCategory>
</PreferenceScreen> 

A continuación se representa la forma en la que Android muestra las categorías:

Práctica: Organizando preferencias(II)

Modifica la práctica anterior para que en lugar de mostrar las propiedades en dos pantallas, aparezcan en una sola, tal y como se muestra en la imagen anterior.

Como se almacenan las preferencias de usuario

Si un usuario modifica el valor de una preferencia, este quedará almacenado de forma permanente en el dispositivo. Para conseguir esta persistencia, Android almacena las preferencias seleccionadas en un fichero XML dentro de la carpeta data/data/nombre.del.paquete/files/shared_prefs, donde nombre.del.paquete ha de ser reemplazado por el paquete de la aplicación. El nombre del fichero para almacenar las preferencias de usuario ha de ser siempre nombre.del.paquete_preferences.xml. Esto significa que solo puede haber unas preferencias de usuario por aplicación. Como se estudiará más adelante, puede haber otros ficheros de preferencias; pero, a diferencia de las preferencias de usuario, no pueden ser editadas directamente por el usuario, si no que hay que acceder a ellas por código.

Ejercicio paso a paso: Donde se almacenan las preferencias de usuario

Veamos donde se han almacenado las preferencias que acabamos de crear:

1.    Para navegar por el sistema de ficheros de un dispositivo, desde Android Studio, selecciona la lengüeta Device File Explorer en la esquina inferior derecha.

2.    Busca el siguiente fichero:  /data/data/<nombre del paquete>/shared_prefs/ <nombre del paquete>_preferences.xml

3. Haz doble clic sobre el fichero para visualizar su contenido:

 
<map>
   <boolean name="notificaciones" value="true" /> 
   <string name="maximo">12</string> 
   <string name="orden">0</string> 
</map>

Accediendo a los valores de las preferencias

Por supuesto, será necesario acceder a los valores de las preferencias para alterar el funcionamiento de nuestra aplicación. El siguiente ejemplo nos muestra cómo realizarlo:

public void mostrarPreferencias(){
   SharedPreferences pref = 
            PreferenceManager.getDefaultSharedPreferences(this);
   String s = "música: " + pref.getBoolean("musica",true)
         +", gráficos: " + pref.getString("graficos","?");
   Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}

El código comienza creando el objeto pref de la clase SharedPreferences y le asigna las preferencias definidas para la aplicación. A continuación crea el String s y le asigna los valores de dos de las  preferencias. Se utilizan los métodos pref.getBoolean() y pref.getString(), que disponen de dos parámetros: el valor de key que queremos buscar ("musica" y "graficos") y el valor asignado por defecto en caso de no encontrar esta key.

Finalmente se visualiza el resultado utilizando la clase Toast. Los tres parámetros indicados son el contexto (nuestra actividad),  el String a mostrar y el tiempo que se estará mostrando esta información.

Ejercicio paso a paso: Accediendo a los valores de las preferencias

1. Copia la función anterior en la actividad principal. Añade el parámetro que se muestra a continuación  mostrarPreferencias(View view).

2. Asígna el atributo onClick del botón Salir o Jugar al mé anterior, según estés en Mislugares o Asteroides.

3. Visualiza también el resto de las preferencias que hayas introducido.

Verificar valores correctos


En muchas ocasiones vas a querer limitar los valores que un usuario puede introducir en las preferencias. Por ejemplo, podría ser interesante que el valor introducido por el usuario en la preferencia número de fragmentos solo pudiera tomar valores entre 0 y 9. Para conseguir esto podemos utilizar el escuchador de evento onPreferenceChangeListener que podremos asignar a una preferencia. Veamos cómo actuar en el siguiente ejercicio:

Ejercicio paso a paso: Verificar valores correctos de una preferencia.

 

1.   Copia este código al final del método onCreate() en PreferenciasFragment:

 
final EditTextPreference fragmentos = (EditTextPreference) 
                                             findPreference("maximo");
fragmentos.setOnPreferenceChangeListener(
  new Preference.OnPreferenceChangeListener() {
    @Override
    public boolean onPreferenceChange(Preference preference, Object 
                                                              newValue) {
       int valor;
       try {
          valor = Integer.parseInt((String)newValue);
       } catch(Exception e) {
          Toast.makeText(getActivity(), "Ha de ser un número", 
                                              Toast.LENGTH_SHORT).show();
          return false;
       }
       if (valor>=0 && valor<=99) {
          fragmentos.setSummary(
               "Limita en número de valores que se muestran ("+valor+")");
          return true;
       } else {
          Toast.makeText(getActivity(), "Valor Máximo 99", 
                                              Toast.LENGTH_SHORT).show();
          return false;
       }
    }
  });
 

NOTA: Si trabajas con Asteroides reemplaza "maximo" por "fragmentos" y cambia los textos necesarios para que correspondan con esta propiedad.

El código comienza obteniendo una referencia de la preferencia para asignarle un escuchador que será llamado cuando cambie su valor. El escuchador comienza convirtiendo el valor introducido a entero. Si se produce un error es porque el usuario no ha introducido un valor adecuado. En este caso, mostramos un mensaje y devolvemos false para que el valor de la preferencia no sea modificado. Si no hay error, tras verificar el rango de valores aceptables, modificamos la explicación de la preferencia para que aparezca el nuevo valor entre paréntesis y devolvemos true para aceptar este valor. Si no está en el rango, mostramos un mensaje indicando el problema y devolvemos false.

2.   Ejecuta el proyecto y verifica que funciona correctamente.

Práctica: Mostrar el valor de una preferencia

En el ejercicio anterior cuando se modifica el número de fragmentos se muestra entre paréntesis el nuevo valor introducido. El funcionamiento no es del todo correcto, cuando entramos por primera vez o cuando se cambia el teléfono de vertical a horizontal este valor no se muestra. Añade el código necesario para que este valor aparezca siempre.

Preguntas de repaso y reflexión: preferencias de usuario