TP4 (2019) – Liste et base de données

L’objet de ce TP est de développer une application pour saisir des mesures (de poids par exemple, mais vous pouvez adapter à vos préférences : température extérieure, mm de précipitations, radioactivité…). Pour cela nous allons introduire l’élément ListView et utiliser une base de données.

NB: Récemment (2017) le container ListView a été remplacé par RecyclerView. Mais ListView reste présent et plus facile à comprendre pour débuter.

Préliminaire

(rien de nouveau par rapport aux précédents TPs, mais on en a besoin !)

Créez une nouvelle application avec 2 activités. La première recevra la liste des mesures, la seconde servira à ajouter une mesure de poids :

  1. Dans la première activité, placez 2 boutons : Ajouter et Quitter (on veillera à utiliser comme layout principal un layout linéaire vertical, et on placera les 2 boutons dans un layout linéaire horizontal en haut.
  2. Dans la seconde activité (elle aussi en layout linéaire vertical), placez 2 éléments : un champs de saisie (décimal) et un bouton « Ajouter ».
  3. Dans le code de l’activité principale, implémentez les actions nécessaires aux 2 boutons : le bouton ajouter va lancer la seconde activité, et le bouton quitter, comme son nom l’indique…
  4. Dans l’activité principale, ajouter un champ de texte temporaire : on pourra l’enlever dans une version suivante, ou bien le recycler pour afficher autre chose. Pour l’instant il va seulement nous servir à vérifier que le retour de la première activité se passe correctement – il n’est donc pas nécessaire de passer du temps à sa présentation. On va juste l’ajouter dans le layout linéaire principal.
  5. Complétez le code des 2 activités pour que la nouvelle saisie soit affichée dans le champ de texte temporaire. Pour l’instant on va transmettre la saisie sous la forme de la chaîne de caractères saisi (type String).
  6. Pour terminer on va, dans l’activité principale, convertir la valeur retournée en double. Pour le débogage, on pourra par exemple afficher cette valeur multipliée par 2 (afin d’être bien sûr que la conversion a été correcte).

2 remarques pour terminer ce préliminaire :

  • Si vous avez bien intégré les précédents cours, cette étape préliminaire ne doit pas vous prendre plus de 20 minutes, 1/2h si vous tapez avec 2 doigts…
  • Si vous n’arrivez pas à faire la conversion en double, vous pouvez garder la valeur sous forme de chaîne de caractères pour les étapes suivantes.

Première étape

Nous allons maintenant conserver toutes les saisies dans une liste. Pour cela on va ajouter sur l’écran principal un widget de type ListView. Vous pouvez conserver la première version en clonant votre projet, c’est à dire en en créant un second (par exemple en weightmonitorV2) à partir d’une copie du premier. Voir le tuto Dupliquer ou renommer un projet android

  1. Ouvrez le layout de l’activité principale. Ajoutez un container de type ListView (vous le trouverez dans la catégorie Legacy) dessous du champs de texte temporaire.
  2. Vérifiez l’id de ce ListView. Il doit être « @android:id/list » (mettez le si ce n’a pas été fait automatiquement).

On va maintenant passez à l’implémentation. Dans le fichier MainActivity.java. L’idée est de récupérer une référence à cette liste et de l’associer à un tableau de données (ici ce sera une liste de chaînes de caractères).

Mais le fonctionnement des containers de type ListView est un peu particulier et il va falloir modifier le type de la classe principale. Au lieu d’Activity (on de AppCompatActivity), on va utiliser ListActivity. Cette classe comprend entre autres une méthode GetListView, qu’on utilisera pour récupérer une référence au container de type ListView (à la place de FindViewById qu’on a l’habitude d’utiliser pour accéder à d’autres objets de l’interface).

  1. Donc on va d’abord récupérer la référence au ListView (dans OnCreate) :
    ListView listView=this.getListView();
    
  2. Puis, on va créer une liste de chaînes de caractères. Comme on aura besoin d’accéder à cette liste depuis d’autres méthodes on va en faire un attribut de la classe:
    private ArrayList<String> maListe;
    

    et dans *OnCreate* :

    maListe=new ArrayList<String>();
    
  3. Enfin on va créer un objet « adapter » qui va servir à adapter notre liste de chaînes de caractères avec le container de type ListView. Dans un premier temps on va utiliser un ArrayAdapter préfabriqué, par la suite on verra qu’on peut en implémenter un. Comme tout à l’heure on aura besoin de cet adapter plus tard, il faut donc en faire un attribut :
    private ArrayAdapter<String> adapter;
    

    et on le crée dans OnCreate avant de l’associer au listView (seconde ligne ci-dessous) :

    adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, maListe);
    listView.setAdapter(adapter);
    
  4. Finalement dans la méthode onActivityResult, on va ajouter la valeur qu’on vient de lire (où wval est la valeur lue, de type double, adaptez si vous avez gardé cette valeur sous la forme d’une chaîne). Il faut ensuite informer l’adapter que la valeur a été modifiée, pour que l’affichage soit mis à jour (second ligne ci-dessous).
    maListe.add(String.format("Val : %f",wval));
    adapter.notifyDataSetChanged();
    

Pour résumer ce qui doit être changé/ajouté par rapport à l’étape préliminaire :

public class MainActivity extends ListActivity implements View.OnClickListener {
    private ArrayList<String> maListe;
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // .....

        maListe=new ArrayList<String>();
        ListView listView=this.getListView();
        adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, maListe);
        listView.setAdapter(adapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
    @Nullable Intent data) {
        // ...
        maListe.add(String.format("Val : %f",wval));
        adapter.notifyDataSetChanged();
        // ...
    }

Exécutez cela dans une machine virtuelle ou sur un téléphone réel, ajoutez suffisamment de lignes pour remplir l’écran. Vous constaterez que lorsque la capacité de l’écran est dépassé, une barre de défilement apparaît automatiquement.


Seconde étape

Le problème maintenant, c’est que lorsqu’on quitte l’application, on perd nos valeurs. Quelle solution proposez vous ? Les stocker dans la base de données du téléphone ? Oui c’est cela 😀

Une fois les données enregistrées dans la base de données on pourra les récupérer à l’ouverture de l’application.

  1. En vous aidant du cours Sauvegarder des données, créez une classe Contrat et une classe DBHelper.
  2. Ensuite dans le onCreate de l’activité principale, on récupère la base grace au DB Helper
  3. puis à l’endroit où vous avez placé la valeur dans liste, enregistrez la aussi dans la base (cf cours, section 4.2).
  4. Il reste à lire les valeurs dans la base au chargement de l’application, pour cela voir la section 4.4 du cours, et à les ajouter à la liste initiale dans onCreate donc.

Quelques indications :

Classe Contrat

class WeightEntriesContract implements BaseColumns {
    public static final String TABLE_NAME = "weightentries";
    public static final String FIELD_ENTRY_ID = "entryid";
    public static final String FIELD_DATE = "date";
    public static final String FIELD_VALUE = "weigh";

    private WeightEntriesContract() {}
}

Classe DBHelper

class WeightMonDBHelper extends SQLiteOpenHelper {
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "MyApp.db";
    private static final String TEXT_TYPE = " TEXT";
    private static final String DATE_TYPE = " DATETIME";
    private static final String DOUBLE_TYPE = " REAL";
    private static final String COMMA_SEP = ",";

    private static final String SQL_CREATE_ENTRIES =
            "CREATE TABLE " +WeightEntriesContract.TABLE_NAME +" ("
                    +WeightEntriesContract._ID +" INTEGER PRIMARY KEY,"
                    +WeightEntriesContract.FIELD_ENTRY_ID +TEXT_TYPE +COMMA_SEP
                    +WeightEntriesContract.FIELD_DATE +DATE_TYPE +COMMA_SEP
                    +WeightEntriesContract.FIELD_VALUE +DOUBLE_TYPE
            +" )";

    private static final String SQL_DELETE_ENTRIES =
            "DROP TABLE IF EXISTS " + WeightEntriesContract.TABLE_NAME;

    public WeightMonDBHelper(@Nullable Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int i, int i1) {
        // nothing to do so far (it's v1)
    }
}

Dans l’activité principale, dans OnCreate :

   WeightMonDBHelper mDbHelper=new WeightMonDBHelper(this);
   db = mDbHelper.getWritableDatabase();

Dans onActivityResult

   ContentValues values = new ContentValues();
   values.put(WeightEntriesContract.FIELD_VALUE, wval);
   long newRowId = db.insert(WeightEntriesContract.TABLE_NAME, null, values);

Étapes suivantes :

  • Ajouter la date de mesure
  • Afficher les dates dans la liste

Ajouter la date de mesure

Pour cette troisième étape, il faut implémenter les actions suivantes (vous disposez de toutes les infos nécessaires pour cela) :

  • Ajouter un champ à la seconde activité
  • Renvoyer la date à l’activité principale
  • Sauvegarder la date dans la base

Afficher les dates dans la liste

Pour la quatrième étabpe, on va créer une classe adapter sur mesure, en la faisant dériver de la classe Adapter. Cela nous permettra d’utiliser un fichier de layout spécifique pour les lignes de la liste. C’est à dire un fichier de layout qui contiendra la mise en page d’une ligne de la liste, et qui sera donc répété avec chacune des entrées à afficher.

to be continued…