Corso programmazione Android Special 1: SharedPreferences e Notifiche

corso programmazione android special 1

Corso programmazione Android Special 1: SharedPreferences e Notifiche

Le altre lezioni del Corso di Programmazione Android sono reperibili a questo indirizzo

Ho deciso di riaprire il corso così da poter descrivere gli ultimi sviluppi dell’app BeReader (i sorgenti della versione 1.1.2 sono scaricabili qui) . Ho implementato una notifica, visualizzata all’avvio dell’applicazione, che ci informa se un prestito supera un numero di giorni preimpostato. Il numero di giorni viene impostato dall’utente tramite le SharedPreferences (possiamo scegliere anche se avere o no la notifica).

SharedPreferences

Con le SharedPreferences possiamo memorizzare sul dispositivo una serie di informazioni (in formato chiave/valore) da utilizzare come impostazioni (o per cosa vogliamo, ho visto gli esempi più disparati).

PreferenceFragmentCompat

Dobbiamo prima di tutto inserire una dipendenza nel file build.glade (e quindi eseguire la sincronizzazione):

...
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    ...
    compile 'com.android.support:support-v4:24.2.1'
    compile 'com.android.support:preference-v7:24.2.1'
}

Ci serve un’activity in grado mostrare le impostazioni disponibili.Necessitiamo di due classi: la prima estende PreferenceFragmentCompat e la seconda estende AppCompatActivity che richiama la prima. Il codice Java del Fragment è molto semplice:

package com.begeekmyfriend.bereader;

import android.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat;


/**
 * Created by fabrizio on 10/10/16.
 */

public class PreferenzeFragment extends PreferenceFragmentCompat { 
    public final String TAG = PreferenzeFragment.class.getSimpleName();

    @Override
    public void onCreatePreferences(Bundle bundle, String rooKey) {
        addPreferencesFromResource(R.xml.settings);
    }

}

L’unico metodo richiamato dal callback onCreatePreferences(), cioè addPreferencesFromResource, legge il layout (che vedremo sotto) e memorizza quanto inserito dall’utente. Vediamo anche il semplice codice dell’activity (non dimentichiamoci di inserirla nel file AndroidManifest.xml):

package com.begeekmyfriend.bereader;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;

public class ImpostazioniActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_impostazioni);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_7);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); //Pulsante di navigazione clickabile
getSupportFragmentManager().beginTransaction()
.replace(R.id.flImp, new PreferenzeFragment())
.commit();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}

}

Il layout del Fragment è un un pò diverso dal solito e non va inserito nella cartella res/layout ma nella cartella res/xml. Vediamo quindi il file res/xml/settings.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/notifiche">
        <CheckBoxPreference
            android:title="@string/vuoi_notifica"
            android:key="not_si_no"
            android:defaultValue="false"/>
        <ListPreference
            android:dialogTitle="@string/quanti_giorni"
            android:entries="@array/num_giorni"
            android:entryValues="@array/num_giorni"
            android:summary="@string/quanti_giorni_2"
            android:key="days"
            android:defaultValue="90"/>
    </PreferenceCategory>
    <PreferenceCategory
        android:title="@string/order">
        <ListPreference
            android:dialogTitle="@string/order"
            android:entries="@array/order_by"
            android:entryValues="@array/order_by_2"
            android:summary="@string/order_by_3"
            android:key="order"
            android:defaultValue="titolo"/>
    </PreferenceCategory>
</PreferenceScreen>

Abbiamo due gruppi <PreferenceCategory> uno per la notifica e uno per l’ordinamento delle registrazioni (vi lascio come esercizio lo studio delle modifiche fatte per ordinamento). Eccovi uno screenshot:

 

Corso programmazione Android Special 1

Esaminiamo un ListPreferences:

<ListPreference
           android:dialogTitle="@string/quanti_giorni"
           android:entries="@array/num_giorni"
           android:entryValues="@array/num_giorni"
           android:summary="@string/quanti_giorni_2"
           android:key="days"
           android:defaultValue="90"/>

Tale componente apre un dialog che permette di scegliere le entries, vediamo cosa impostiamo:

  • dialogTitle: titolo del dialog
  • entries: lette direttamente dall’array num_giorni
  • entryValues: il valore associato all’entries letto sempre dall’array num_giorni
  • summary: la descrizione della voce
  • keys: il nome della chiave (da usare nel codice java)
  • defaultValues: valore da associare come default

Vediamo gli array definiti in res/values/array.xml:

    ...
    <array name="ar_si_no">
        <item>@string/no</item>
        <item>@string/si</item>
    </array>
    <array name="num_giorni">
        <item>@string/sessanta</item>
        <item>@string/novanta</item>
        <item>@string/centoottanta</item>
        <item>@string/trecentosessantacinque</item>
    </array>
    <array name="order_by">
        <item>@string/titolo</item>
        <item>@string/autore</item>
        <item>@string/tipologia</item>
        <item>@string/formato</item>
        <item>@string/genere</item>
    </array>
    <array name="order_by_2">
        <item>titolo</item>
        <item>autore</item>
        <item>tipologia</item>
        <item>formato</item>
        <item>genere</item>
    </array>
    ...

L’activity dalla versione 1.1.2 viene richiamata direttamente dal Navigation Drawer.

Leggiamo le impostazioni

Ho deciso che la notifica deve apparire all’avvio dell’applicazione quindi andiamo a modificare la classe MainActivity.java (riporto qui solo le aggiunte legate alle preferenze):

...
import android.content.SharedPreferences;
...

public class MainActivity extends AppCompatActivity {

    ....
    SharedPreferences settings;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        settings = PreferenceManager.getDefaultSharedPreferences(this); //carico le preferenze
       ...
}

public void scadenza_prestiti() {
        boolean not_si = settings.getBoolean("not_si_no", false); //leggo notifica si o no
        String gg = settings.getString("days",  "90"); //leggo i giorni
        int gg_conv = Integer.parseInt(gg); //li traformo così li posso confrontare
        ...
    }

In onCreate definiamo l’oggetto settings e leggiamo le preferenze direttamente durante l’instanziamento ( PreferenceManager.getDefaultSharedPreferences(this) ), nel metodo scadenza_prestiti, che più avanti riporterò interamente, leggiamo con una semplice riga la entry desiderata dalle Sharedpreferences o mettiamo un valore di default se la preferenza viene trovata vuota ( settings.getString(“days”, “90”) ).

Notifiche

Per mostrare la notifica solo all’avvio dobbiamo richiamare il nostro nuovo metodo in onCreate all’interno dell’if seguente

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        if (savedInstanceState == null) { //Se non c'è uno stato salvato
            ....
            scadenza_prestiti(); //Versione 1.1.x
            try {
                fragment = (Fragment) fragmentClass.newInstance(); //Creo una nuova istanza di Fragment
            } catch (Exception e) {
                e.printStackTrace();
            }
            ...
        }
    }

Il metodo scadenza_prestiti è un pò incasinato, sopratutto per alcune scelte un pò sbagliate fatte in precedenza (vi lascio indovinare quali leggendo la classe Uitlity utilizzata qui per “giocare” con le date), e una volta lette le preferenze prepara la notifica solo se:

  • abbiamo messe si nella checkbox Notifica si/no
  • la differenza fra la data odierna e la data di inizio prestito è maggiore o uguale ai giorni definiti (60, 90, 180 o 365)
public void scadenza_prestiti() {
        boolean not_si = settings.getBoolean("not_si_no", false); //leggo notifica si o no
        String gg = settings.getString("days",  "90"); //leggo i giorni
        int gg_conv = Integer.parseInt(gg); //li traformo così li posso confrontare
        db = new DatabaseBeReader(this);
        db.open();
        c = db.fetchByBerPres(DatabaseBeReader.BeReaderMetaData.TITOLO); //ordino per titolo di default
        if (not_si == true) //vuoi la notifica
        {
            if ( c.getCount() > 0) //Se ho almeno un componente controllo
            {
                while (c.moveToNext()) { //inizio a scorrere
                    String data = c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderPresMetaData.PRESTATO_QUANDO));
                    Utility ut = new Utility(this);
                    long diff;
                    diff = ut.diffData(data); //calcolo la differenza in giorni con la data attuale
                    if (diff >= gg_conv) { //se i giorni sono più di quelli specificati nelle preferenze
                        Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); //Recupero il suono di default
                        NotificationCompat.Builder myNot = new NotificationCompat.Builder(this)
                                .setContentTitle(getResources().getString(R.string.app_name))
                                .setContentText(getResources().getString(R.string.avviso_pres_1) +
                                        " " + getResources().getString(R.string.avviso_pres_2) + " " + diff + " " + getResources().getString(R.string.avviso_pres_3))
                                .setSmallIcon(R.mipmap.geek)
                                .setSound(sound)
                                .setPriority(-1);
                        NotificationManager notifMan = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        notifMan.notify(0, myNot.build());
                    }
                }
            }
        }
    }

Analizziamo la sola parte di creazione della notifica:

Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); //Recupero il suono di default
NotificationCompat.Builder myNot = new NotificationCompat.Builder(this)
                .setContentTitle(getResources().getString(R.string.app_name))
                .setContentText(getResources().getString(R.string.avviso_pres_1) +
                     " " + getResources().getString(R.string.avviso_pres_2) + " " + diff + " " + getResources().getString(R.string.avviso_pres_3))
                .setSmallIcon(R.mipmap.geek)
                .setSound(sound)
                .setPriority(-1);
NotificationManager notifMan = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notifMan.notify(0, myNot.build());

Iniziamo leggendo l’uri della suoneria di default delle notifiche, dopo di che creiamo un NoticationBuilder e impostiamo:

  • Titolo delle notifica: setContentTitle()
  • Testo della notifica: setContentText()
  • Icona: setSmallIcon()
  • Il suono di notifica: setSound()
  • e la priorità: setPriority. Col il valore-1 impostiamo la priorità media, quindi dovremmo vedere la notifica anche nel lockscreen del dispositivo

Dopo di che associamo il Builder al NotificationManager. Ecco uno screenshot della notifica:

 

Corso programmazione Android Special 1

Le import necessarie sono:

...
import android.app.NotificationManager;
...
import android.media.RingtoneManager;
import android.net.Uri;
...
import android.support.v4.app.NotificationCompat;
import android.support.v7.preference.PreferenceManager;
...

La lezione è finita alla prossima.

Precedentemente pubblicato su Be Geek My Friend

You May Have Missed