Corso programmazione Android Lezione 12: L’app BeReader Parte 1

corso programmazione android lezione 12

Corso programmazione Android Lezione 12: L’app BeReader Parte 1

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

In questa prima parte vedremo di riprendere le redini della nostra app.
Infatti, pur attenendomi alle specifiche originali, sono andato molto avanti con lo sviluppo (credo di poter rilasciare a breve la versione 1.0), quindi urge un riepilogo.
Il codice sorgente mostrato in questa lezione è reperibile qui.

Fragment principali

Come da specifiche la nostra App è dotata di un Navigation Drawer che richiama le categorie principali:

  • Home: mostra tutte le registrazioni in ordine alfabetico
  • Libri: mostra tutte le registrazioni relative ai libri
  • Fumetti: mostra tutte le registrazioni relative ai fumetti
  • Dischi: mostra tutte le registrazioni relative ai dischi
  • Prestiti: mostra tutte le registrazione nelle quali è stato registrato un prestito (sarà oggetto della lezione 14)

Tutte le registrazioni sono in ordine alfabetico, le categorie libri, fumetti e dischi permettono l’inserimento di una nuova registrazione.

corso programmazione android lezione 12

Vediamo come è costruito il Layout (file layout/home_fragment.xml) di uno dei Fragment principali (come descritto nelle precedenti lezioni il Drawer è gestito grazie ai fragment)

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingLeft="10dp"
  android:paddingRight="10dp"
  android:background="@drawable/paper_tile">
  <ListView
    android:id="@+id/home_lv"
    android:layout_marginTop="10dp"
    android:layout_marginBottom="10dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@android:color/transparent"
    android:dividerHeight="10dp"/>
</android.support.design.widget.CoordinatorLayout>

La spazio che separa gli elementi è definito dal parametro android:dividerHeight=”10dp” all’interno della ListView.
Ripassiamo il layout di un singolo elemento (file layout/cursor_layout.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@drawable/border_layout">
    <TextView
        android:id="@+id/raw1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAlignment="center"
        android:textStyle="bold"
        android:textSize="20sp"
        android:layout_gravity="center"/>
    <TextView
        android:id="@+id/raw2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAlignment="center"
        android:textSize="20sp"
        android:layout_gravity="center"/>
</LinearLayout>

e la definizione di forma e sfondo (file drawable/border:layout.xml)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <padding
    android:left="15dp"
    android:top="10dp"
    android:right="15dp"
    android:bottom="10dp"/>
  <stroke
    android:width="1dp"
    android:color="#D8FDFB"/>
  <corners android:radius="5dp"/>
  <solid android:color="#90FFFFFF"/>
</shape>

Il codice Java è relativamente semplice e non dovrebbe aver subito modifiche (file LibriFragment.java):

package com.begeekmyfriend.bereader;

import android.app.Fragment;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

/**
 * Created by fabrizio on 12/03/16.
 */
public class LibriFragment extends Fragment{

    private ListView libriLV;
    DatabaseBeReader db;
    Cursor c, c_resume;
    SimpleCursorAdapter cur, cur_resume;

    @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_libri, container, false);
        libriLV = (ListView) rootView.findViewById(R.id.libri_lv);
        db = new DatabaseBeReader(rootView.getContext());
        db.open();
        c = db.fetchByTipologia(getResources().getString(R.string.libri));
    cur = new SimpleCursorAdapter(
            getActivity(),
      R.layout.cursor_layout,
      c,
      new String[] {DatabaseBeReader.BeReaderMetaData.TITOLO, DatabaseBeReader.BeReaderMetaData.AUTORE},
      new int[]{R.id.raw1 , R.id.raw2},
      0);
        libriLV.setAdapter(cur);
        libriLV.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                db.open();
                Intent intent = new Intent(getActivity(), VisualizzaLibri.class); //apro una nuova activity
                String pkg = getActivity().getPackageName();
                //Passo come parametro alla nuova Activity l'id della registrazione
                intent.putExtra(pkg+".ID",  c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.ID)));
                startActivity(intent);
            }
        });
        db.close();
        FloatingActionButton fab = (FloatingActionButton) rootView.findViewById(R.id.fb_add_libri);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getActivity(), AggiungiLibri.class);
        startActivity(intent);
            }
        });

        return rootView;
    }

    @Override
   public void onResume()
   {
       super.onResume();
       db.open();
       c =  db.fetchByTipologia(getResources().getString(R.string.libri));
       //c_resume = db.fetchByTipologia(getResources().getString(R.string.libri));
       cur_resume = new SimpleCursorAdapter(
            getActivity(),
      R.layout.cursor_layout,
      c,
      new String[] {DatabaseBeReader.BeReaderMetaData.TITOLO, DatabaseBeReader.BeReaderMetaData.AUTORE},
      new int[]{R.id.raw1 , R.id.raw2},
      0);
       libriLV.setAdapter(cur_resume);
       db.close();
   }
}

Il Database

Inizialmente avevo pensato di utilizzare una sola tabella per tutta l’applicazione, poi mi sono scontrato con alcuni problemi tecnici. Il più difficile da risolvere (e che mi ha portato poi a staccare il db) è relativo al DatePicker (un componente che vedremo nella prossima lezione) che non può restituire il valore null, quindi ci trovavamo a inserire un valore non coerente con la realtà (prestito = no, prestato_chi = null e prestato_quando = data odierna). Il problema si poteva risolvere in più modi, ho scelto quello che apre più possibilità, una tabella separata per i prestiti.

Se qualcuno ha già installato l’app dai sorgenti allegati alle precedenti lezioni è necessario disinstallarla e reinstallarla, essendo variati i campi del db (oppure cancellare i dati, ma disinstallare è più sicuro).
Vediamo alcuni spezzoni della nuova versione della classe DatabaseBeReader (file DatabaseBeReader.java)

Definizione di campi e creazione delle tabelle

static class BeReaderMetaData
    {
        static final String BEREADER_TABLE = "bereader_table";
        static final String ID = "_id";
        static final String TIPOLOGIA = "tipologia";
        static final String FORMATO = "formato";
        static final String TITOLO = "titolo";
        static final String AUTORE = "autore";
        static final String GENERE = "genere";
        static final String POSIZIONE = "posizione";
    }

    private static final String BEREADER_TABLE_CREATE = "CREATE TABLE IF NOT EXISTS "
        + BeReaderMetaData.BEREADER_TABLE + " ("
        + BeReaderMetaData.ID+ " integer primary key autoincrement, "
        + BeReaderMetaData.TIPOLOGIA+  " text not null, "
        + BeReaderMetaData.FORMATO+  " text not null, "
        + BeReaderMetaData.TITOLO+ " text not null, "
        + BeReaderMetaData.AUTORE+ " text,"
        + BeReaderMetaData.GENERE+ " text,"
        + BeReaderMetaData.POSIZIONE+ " text" //,"
        //+ BeReaderMetaData.PRESTATO+ " text not null,"
        //+ BeReaderMetaData.PRESTATO_CHI+ " text,"
        //+ BeReaderMetaData.PRESTATO_QUANDO + " text"
        +");";

    static class BeReaderPresMetaData
    {
        static final String BEREADER_PRES_TABLE = "bereader_pres_table";
        static final String ID = "_id";
        static final String ID_BER = "id_ber";
        static final String PRESTATO_CHI = "prestato_chi";
        static final String PRESTATO_QUANDO = "prestato_quando";
        static final String PRESTATO_FINE = "prestato_fine";
    }

     private static final String BEREADER_PRES_TABLE_CREATE = "CREATE TABLE IF NOT EXISTS "
        + BeReaderPresMetaData.BEREADER_PRES_TABLE + " ("
        + BeReaderPresMetaData.ID+ " integer primary key autoincrement, "
        + BeReaderPresMetaData.ID_BER+ " integer, "
        + BeReaderPresMetaData.PRESTATO_CHI+ " text,"
        + BeReaderPresMetaData.PRESTATO_QUANDO + " text,"
        + BeReaderPresMetaData.PRESTATO_FINE + " text"
        +");";

Come si vede abbastanza chiaramente ora abbiamo una tabella bereader_table contente i campi:

  • id
  • tipologia
  • formato
  • titolo
  • autore
  • genere
  • posizione

e una nuova tabella bereader_pres_table contenente i campi:

  • id
  • id_ber (l’id della registrazione alla quale è associato il prestito, quindi della tabella precedente)
  • prestato_chi
  • prestato_quando
  • prestato_fine

Il campo prestato_fine al momento non è gestito l’ho messo per avere un campo da poter riutilizzare.

Inserimento delle registrazioni

public void inserisci(String tipologia, String formato, String titolo,String autore, String genere, String posizione)//,String prestato, String prestato_chi, String prestato_quando)
    {
        ContentValues cv = new ContentValues();
        cv.put(BeReaderMetaData.TIPOLOGIA, tipologia);
        cv.put(BeReaderMetaData.FORMATO, formato);
        cv.put(BeReaderMetaData.TITOLO, titolo);
        cv.put(BeReaderMetaData.AUTORE, autore);
        cv.put(BeReaderMetaData.GENERE, genere);
        cv.put(BeReaderMetaData.POSIZIONE, posizione);
        //cv.put(BeReaderMetaData.PRESTATO, prestato);
        //cv.put(BeReaderMetaData.PRESTATO_CHI, prestato_chi);
        //cv.put(BeReaderMetaData.PRESTATO_QUANDO, prestato_quando);
        mDb.insert(BeReaderMetaData.BEREADER_TABLE, null, cv);
    }

    public void inserisci_pres(String id_ber, String prestato_chi, String prestato_quando,String prestato_fine)
    {
        ContentValues cv = new ContentValues();
        cv.put(BeReaderPresMetaData.ID_BER, id_ber);
        cv.put(BeReaderPresMetaData.PRESTATO_CHI, prestato_chi);
        cv.put(BeReaderPresMetaData.PRESTATO_QUANDO, prestato_quando);
        cv.put(BeReaderPresMetaData.PRESTATO_FINE, prestato_quando);
        mDb.insert(BeReaderPresMetaData.BEREADER_PRES_TABLE, null, cv);
    }

Ci serve un metodo per ognuna delle tabelle

Query

public Cursor fetch() //Ordinato per titolo
  {
    return mDb.query(BeReaderMetaData.BEREADER_TABLE, null, null, null, null, null, BeReaderMetaData.TITOLO);
    }

    public Cursor fetchByTipologia(String tipologia) //Ordinato per titolo
    {
        return mDb.query(BeReaderMetaData.BEREADER_TABLE, null, BeReaderMetaData.TIPOLOGIA+"='"+tipologia+"'", null, null, null, BeReaderMetaData.TITOLO);
    }

    public Cursor fetchById(String _id)
    {
        return mDb.query(BeReaderMetaData.BEREADER_TABLE, null, BeReaderMetaData.ID+"='"+_id+"'", null, null, null, null);
    }

    public Cursor fetchPresById(String _id)
    {
        return mDb.query(BeReaderPresMetaData.BEREADER_PRES_TABLE, null, BeReaderPresMetaData.ID_BER+"='"+_id+"'", null, null, null, null);
    }

    public Cursor fetchByPres(String prestito) //Ordinato per titolo
    {
        return mDb.query(BeReaderPresMetaData.BEREADER_PRES_TABLE, null, null,  null, null, null, null);
        //BeReaderPresMetaData.PRESTATO+"='"+prestito+"'", null, null, null, null);
    }


    public void editById(String id, String campo, String nuovo_valore)
    {
        String editQuery = "UPDATE "+ BeReaderMetaData.BEREADER_TABLE+" SET "+campo+" = '"+nuovo_valore+"' WHERE "+ BeReaderMetaData.ID+"= '"+id+"';";
        mDb.execSQL(editQuery);
    }

Sulle query ho un po abbondato (non credo neanche di usarli tutti), passando al metodo mDd.query un campo come argomento fisso l’ordinamento, passando invece il terzo argomento possiamo filtrare la query.

Cancellazione

public void cancellaById(String id)
    {
        String deleteQuery = "DELETE FROM "+BeReaderMetaData.BEREADER_TABLE+" WHERE "+BeReaderMetaData.ID+"='" + id + "';";
        String deleteQuery2 = "DELETE FROM "+BeReaderPresMetaData.BEREADER_PRES_TABLE+" WHERE "+BeReaderPresMetaData.ID_BER+"='" + id + "';";
        mDb.execSQL(deleteQuery);
        mDb.execSQL(deleteQuery2);
    }

    public void cancellaPresById(String id)
    {
        String deleteQuery = "DELETE FROM "+BeReaderPresMetaData.BEREADER_PRES_TABLE+" WHERE "+BeReaderPresMetaData.ID+"='" + id + "';";
        mDb.execSQL(deleteQuery);
    }

Anche qui serve un metodo per ogni tabella

SQLiteOpenHelper

private class DbHelper extends SQLiteOpenHelper
    {
        public DbHelper (Context context, String name, CursorFactory factory, int version)
        {
            super(context, name, factory, version);
        }

        @Override
        public void onCreate(SQLiteDatabase _db)
        {
            _db.execSQL(BEREADER_TABLE_CREATE);
            _db.execSQL(BEREADER_PRES_TABLE_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase _db, int oldVersion, int newVersion)
        {
            //TODO: Implement this method
        }
    }

Anche in questa classe innestata dobbiamo aggiungere la creazione per entrambe le tabelle.

Aggiunta registrazioni

Questa classe ha subito solo un taglio (dovuto all’eliminazione di alcuni campi dalla tabella del Db) quindi ripassiamola (file AggiungiLibri.java)

package com.begeekmyfriend.bereader;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.Toast;

public class AggiungiLibri extends Activity {

    //Defnisco istanze degli oggetti
    EditText et_titolo, et_autore, et_genere, et_posizione;
    Spinner spinner_formato; 
    ImageButton button_salva;
    ArrayAdapter<CharSequence> aa_formato; 
    DatabaseBeReader db;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aggiungi_libri);
        et_titolo = (EditText) findViewById(R.id.et_titolo); //Associo id all'oggetto
        et_autore = (EditText) findViewById(R.id.et_autore);
        spinner_formato = (Spinner) findViewById(R.id.spinner_formato);
        //Creo un array adapter utilizzando l'array di stringhe
        aa_formato = ArrayAdapter.createFromResource(this, R.array.ar_formato_libri,
                android.R.layout.simple_spinner_item);
        //Associo un layout di sistema
        aa_formato.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner_formato.setAdapter(aa_formato); //Associo l'adapter allo spinner
        et_genere = (EditText) findViewById(R.id.et_genere);
        et_posizione = (EditText) findViewById(R.id.et_posizione);
        button_salva = (ImageButton) findViewById(R.id.ib_salva);
        button_salva.setOnClickListener(new View.OnClickListener() { //Classe anonima per il clicj dell'image button
            @Override
            public void onClick(View v) {
                salva(); //Richiamo metodo per il salvataggio
            }
        });
    }

    public void salva() {
        db = new DatabaseBeReader(this); //Denisco istanza dell'oggetto db e richiamo il costruttore
        db.open(); //Apro il db
        if (et_titolo.getText().toString().equals("")) { //Controllo che il titolo sia valorizzato
            Toast toast = Toast.makeText(getApplicationContext(), getResources().getString(R.string.tit_no_val), Toast.LENGTH_SHORT); //Mosto un messaggio
            toast.show();
        } else { //OK è valorizzato quindi inserisco
            //Inserisco nel db il contenuto dei campi
            db.inserisci(getResources().getString(R.string.libri), spinner_formato.getSelectedItem().toString(),
                    et_titolo.getText().toString(), et_autore.getText().toString(), et_genere.getText().toString(),
                    et_posizione.getText().toString());
            db.close(); //Chiudo il db
            finish(); //chiudo il dialog
        }
    }
}

Come sempre definiamo tutti i campi e intercettiamo il tap sul bottone richiamando il metodo salva(), il quale si occupa di richiamare la funzione di inserimento definita nella classe DatabaseBeReader.
Le classi di aggiunta libri, fumetti e dischi sono praticamente identiche, cambia solo la tipologia nel metodo salva.
Il layout è piuttosto semplice eccolo (file layout/activity_aggiungi_libri.xml):

<?xml version="1.0" encoding="utf-8"?>
<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:fitsSystemWindows="true"
    tools:context=".AggiungiLibri">

    <include layout="@layout/content_aggiungi" />


</RelativeLayout>

Per pulizia l’abbiamo diviso in due ecco il resto (file layout/content_aggiungi.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".AggiungiLibri"
    tools:showIn="@layout/activity_aggiungi_libri">

    <ScrollView
        android:id="@+id/sv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv1_libri"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/titolo"/>
            <EditText
                android:id="@+id/et_titolo"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/autore_ins"/>
            <EditText
                android:id="@+id/et_autore"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/formato"/>
            <Spinner
                android:id="@+id/spinner_formato"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:spinnerMode="dropdown" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/genere"/>
            <EditText
                android:id="@+id/et_genere"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/posizione"/>
            <EditText
                android:id="@+id/et_posizione"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

             </LinearLayout>
    </ScrollView>

    <ImageButton
        android:id="@+id/ib_salva"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:text="@string/salva"
        android:src="@mipmap/ic_action_save"/>

</RelativeLayout>

L’activity è stata trasformata in un Dialog grazie alla definizione del tema nel file AndroidManifest.xml vediamo la porzione di codice relativa:

<activity
            android:name=".AggiungiLibri"
            android:label="@string/title_activity_aggiungi_libri"
            android:theme="@style/Theme.AppCompat.Dialog" />

Nella prossima lezione continueremo con l’analisi.

Originariamente pubblicato su Be Geek My Friend

You May Have Missed