Este año estoy participando en el concurso universitario de software libre, concretamente en el proyecto SWADroid. Mi intención es explicar todo lo que vaya aprendiendo mientras participo en el desarrollo del proyecto. Este primer artículo comienza un historial que llevará cuenta de todos los avances logrados.

La primera tarea que se me asignó fue lograr que el diálogo para introducir la contraseña de usuario en las preferencias se mostrara vacío. Ya que al cifrar la contraseña para almacenarla en el diálogo se mostraba la contraseña cifrada (Con los típicos •••), lo cual podía ser algo desconcertante para el usuario, ya que no veía un número de • que correspondieran con la longitud de su contraseña, si no con la contraseña cifrada.

Para explicar cómo conseguir este comportamiento escribiremos una aplicación trivial, enfocada únicamente a explicar todo el proceso.



Actividad Principal

El único propósito de esta actividad es abrir un menú que lanzará nuestras preferencias. Para simplificar el código se muestra sin las sentencias import y package:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case 0:
                startActivity(new Intent(this, MyPrefecenceActivity.class));
                break;

            default:
                break;
        }
        startActivity(new Intent(this, MyPrefecenceActivity.class));
        return true;
    }
}

En el método onOptionsItemSelected comprobamos qué elemento del menú se pulsó y abrimos la correspondiente preferencia, mostraremos a continuación el código para dicha actividad.

Crear una Actividad para las Preferencias

public class MyPrefecenceActivity extends PreferenceActivity implements OnPreferenceChangeListener {

    private Preference mDialogoNormal;
    private Preference mDialogoPersonalizado;
    private SharedPreferences.Editor mEditorDialogoNormal;
    private SharedPreferences.Editor mEditorDialogoPerso;
    private String mPasswordDialogoNormal;
    private String mPasswordDialogoPerso;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        addPreferencesFromResource(R.xml.preferences);
        mDialogoNormal = findPreference("diagNormal");
        mDialogoPersonalizado = findPreference("diagPerso");

        mDialogoNormal.setOnPreferenceChangeListener(this);
        mDialogoPersonalizado.setOnPreferenceChangeListener(this);

        mEditorDialogoNormal = mDialogoNormal.getEditor();
        mEditorDialogoPerso = mDialogoPersonalizado.getEditor();
        mPasswordDialogoNormal = mDialogoNormal.getSharedPreferences().getString("diagNormal", "");
        mPasswordDialogoPerso = mDialogoPersonalizado.getSharedPreferences().getString("diagPerso","");

        if (!mPasswordDialogoNormal.isEmpty())
            mDialogoNormal.setSummary("********");
        if (!mPasswordDialogoPerso.isEmpty())
            mDialogoPersonalizado.setSummary("********");
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {

        String passw = newValue.toString();

        try {
            passw = cifrar(passw);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        if (preference.getKey().equals("diagNormal")) {

            mEditorDialogoNormal.putString("diagNormal", passw);
            mPasswordDialogoNormal = passw;
            mEditorDialogoNormal.commit();
            mDialogoNormal.setSummary("********");

        } else if (preference.getKey().equals("diagPerso")) {

            mEditorDialogoPerso.putString("diagPerso", passw);
            mPasswordDialogoPerso = passw;
            mEditorDialogoPerso.commit();
            mDialogoPersonalizado.setSummary("********");
        }

        return true;
    }

    @Override
    protected void onPause() {
        super.onPause();

        mEditorDialogoNormal.putString("diagNormal", mPasswordDialogoNormal);
        mEditorDialogoNormal.commit();

        mEditorDialogoPerso.putString("diagPerso", mPasswordDialogoPerso);
        mEditorDialogoPerso.commit();
    }

    public String cifrar(String passw) throws NoSuchAlgorithmException {
        String cifrada;
        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(passw.getBytes());
        cifrada = Base64.encodeToString(md.digest(), Base64.DEFAULT);

        return cifrada;
    }
}

Aunque esta no es la mejor forma de hacerlo, para ilustrar la diferencia entre ambas preferencias se almacenarán como dos objetos distintos. mDialogoNormal se comportará como una preferencia por defecto, en este caso es un EditTtextPreference, mDialogoPersonalizado, como su nombre indica, será la preferencia sobre la cual implementaremos nosotros el diálogo. En esta actividad que extiende de PreferenceActivity inicializamos todos los objetos necesarios en el onCreate. El método que realmente nos interesa es onPreferenceChange, que será llamado cuando se detecte algún cambio en los datos de las preferencias. En este caso se pretende mostrar cómo implementar una preferencia para almacenar una contraseña. Cuando el usuario introduce en el diálogo su contraseña, ésta será cifrada en el método cifrar y será éste el valor almacenado en el archivo de preferencias. Con el diálogo normal, si el usuario vuelve a lanzar el diálogo con una contraseña ya almacenada verá esto:

Lo cual corresponde a la contraseña cifrada, lo ideal sería que el cuadro de texto apareciera vacío, para ello crearemos nosotros mismos el diálogo extendiendo de DialogPreference.

public class MyDialogPreference extends DialogPreference {

    private EditText mEditTextPassword;

    public MyDialogPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        setPersistent(false);
        setDialogLayoutResource(R.layout.dialog_password);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {

        if (which == DialogInterface.BUTTON_POSITIVE) {
            String value = mEditTextPassword.getText().toString();
            callChangeListener(value);
        }
        super.onClick(dialog, which);
    }

    @Override
    protected void onBindDialogView(View view) {
        mEditTextPassword = (EditText) view.findViewById(R.id.etpPassword);

        super.onBindDialogView(view);
    }
}

En el constructor de esta clase es necesario establecer el layout que usará el diálogo, en este caso un simple EditText:

<?xml version='1.0' encoding='utf-8'?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical">
<edittext android:id="@+id/etpPassword" android:name="DialogoPersonalidazo" android:layout_width="fill_parent" android:layout_height="wrap_content" android:defaultvalue="" android:hint="dialogoPersonalizado" android:key="diagPerso" android:password="true" android:title="Dialogo Personalizado">
</edittext></linearlayout>

El método callChangeListener llamará a onPreferenceChange, donde decidiremos qué hacer con el valor de la preferencia, si guardarlo o descartarlo.

Con esto ya está todo listo, simplemente falta hacer referencia al diálogo que acabamos de crear en el archivo de preferencias de la siguiente forma:

<?xml version='1.0' encoding='utf-8'?>
<preferencescreen xmlns:android="http://schemas.android.com/apk/res/android">
<preferencecategory android:title="TituloCategoría">
<edittextpreference android:name="DialogoNormal" android:defaultvalue="" android:hint="dialogonormal" android:key="diagNormal" android:password="true" android:title="Dialogo Normal">
<com.example.dialogpreferenceexample.mydialogpreference android:name="DialogoPersonalidazo" android:defaultvalue="" android:hint="dialogoPersonalizado" android:key="diagPerso" android:password="true" android:title="Dialogo Personalizado">
</com.example.dialogpreferenceexample.mydialogpreference></edittextpreference></preferencecategory>
</preferencescreen>

Donde com.example.dialogpreferenceexample.MyDialogPreference es el nombre de la clase del diálogo personalizado.

Referencias

Concise way of writing new DialogPreference classes? »» stackoverflow.com
Custom EditTextPreference and setOnPreferenceChangeListener not called »» stackoverflow.com

Índice