JavascriptProva

Visualizzazione post con etichetta database. Mostra tutti i post
Visualizzazione post con etichetta database. Mostra tutti i post

giovedì 10 maggio 2018

Pulsante che attiva sul server il download personalizzato a seconda della transazione.

Ecco: ho creato un file dataselector.php, che ha a che fare con il database.
<?php
$servername="localhost";
$username="XXXX";
$password="XXXX";
$database="id3147737_db";

$conn=new mysqli($servername,$username,$password,$database);

if($conn->connect_error){
    die("Connessione non riuscita" . $conn->connect_error);
}

$sql="SELECT * FROM Transazioni WHERE id=" .$_POST['id'];
$result=$conn->query($sql);
if($result->num_rows >0){
    while($row = $result->fetch_assoc()){
        echo "id: " . $row["id"] . " - nome: " . $row["nome"] . "
"; } }else{ echo "Nessun record trovato"; } $conn->close(); ?>
... e nel file index.php (che in futuro sarà la e-mail mandata al cliente) ci metto il form:
<form action="dataselector.php" method="POST">
<input type="hidden" name="id" value=1>
<input type="submit"> 
Nel file dataselector.php ho scritto così l'istruzione sql:
$sql="SELECT * FROM Transazioni WHERE id=" .$_POST['id'];
in modo da selezionare nel database Transazioni il record dove il campo id equivale a quello inviato dal form sotto forma di input type="hidden" (cosa che un giorno farà la e-mail mandata al cliente per lo scaricamento) E così con un input type="hidden" name="id" value=1 ho ottenuto il valore corrispondente a ID = 1:
id: 1 - nome: Downloads/gerbert.png


Ma basta cambiare il valore dell'input di nome "id":
<form action="dataselector.php" method="POST">
<input type="hidden" name="id" value=2>
<input type="submit"> 
e ottengo:
id: 2 - nome: Downloads/abruzzo.png

e invece con il valore 3:
<form action="dataselector.php" method="POST">
<input type="hidden" name="id" value=3>
<input type="submit"> 
...ottengo:
id: 3 - nome: Downloads/bovino.jpg
Perfetto! Adesso devo trovare il modo migliore per fare il download.

Trovato. il link è questo Ho fatto il copia-incolla e sono riuscito a downloadare i files che avevo nella cartella sul server.
Domani mi studio meglio il codice.

mySQLi: SELECT con clausola WHERE.

<?php
$servername="localhost";
$username="XXXX";
$password="XXXX";
$database="id3147737_db";

$conn=new mysqli($servername,$username,$password,$database);

if($conn->connect_error){
    die("Connessione non riuscita" . $conn->connect_error);
}

$sql="SELECT * FROM Transazioni WHERE id=2";
$result=$conn->query($sql);
if($result->num_rows >0){
    while($row = $result->fetch_assoc()){
        echo "id: " . $row["id"] . " - nome: " . $row["nome"] . "
"; } }else{ echo "Nessun record trovato"; } ?>
E funziona:
id: 2 - nome: Downloads/abruzzo.png


mercoledì 30 novembre 2016

Cancellare un record in risposta a un long click sulla ListView

Obiettivo: Cancellare un record in risposta a un long click sulla ListView.
Costruire nell'ambito dell'Helper la funzione che cancella un record dalla tabella del database.

    //funzione che cancella un record dalla tabella tblnomi del database
    public void cancella(String s){
        SQLiteDatabase db=this.getWritableDatabase();
        db.delete("tblnomi","nome=?",new String[]{s});

    }
Adesso la uso:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                helper.cancella("Giuseppe");
                return true;
            }
        });
Al long click di qualunque elemento della ListView dovrei avere la cancellazione del record il cui campo "nome" equivale a "Giuseppe".
Proviamolo...

Sì, me lo ha cancellato.
Ho dimenticato di ricaricare l'adapter, però:
Procedo (con un altro nome perché di Giuseppe ce n'era uno solo).
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                helper.cancella("Giovanni");
                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                crs=myCursorAdapter.swapCursor(c);
                return true;
            }
        });
Sì, funziona.

Ora devo però rendere la cosa specifica: devo risalire al nome riportato nell'elemento della listView.
Ecco, dopo aver trovato e sperimentato il modo di accedere alle textView in cui sono riportati nome e cognome, modifico la funzione di helper in modo da includere anche il cognome:
    //funzione che cancella un record dalla tabella tblnomi del database
    public void cancella(String n, String c){
        SQLiteDatabase db=this.getWritableDatabase();
        db.delete("tblnomi","nome=? and cognome=?",new String[]{n,c});
...e riscrivo la funzione in Main:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                TextView txtNome=(TextView)view.findViewById(R.id.txtNome);
                TextView txtCognome=(TextView)view.findViewById(R.id.txtCognome);
                helper.cancella(txtNome.getText().toString(),txtCognome.getText().toString());
                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                crs=myCursorAdapter.swapCursor(c);
                return true;
            }
        });
Vediamo se funziona...

Funziona egregiamente!!! Obiettivo raggiunto!

sabato 26 novembre 2016

Database SQLite: ottenere record unici per un campo o per una combinazione di campi.

Ho re-imparato come rendere unici i records di un database, rendendo unico il valore di un campo e rendendo unica una combinazione di valori di campi.

Rendere unico il valore di un singolo campo:
public class Helper extends SQLiteOpenHelper {
    public Helper(Context context) {
        super(context, "database.db",null,1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE NOMI(_ID INTEGER PRIMARY KEY, " +
                "NOME TEXT NOT NULL UNIQUE, COGNOME TEXT)");
    }

    public void save(String nome, String cognome){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME", nome);
        values.put("COGNOME", cognome);
        try {
            db.insertOrThrow("NOMI", null, values);
        }catch(Exception e){
            System.out.println("ERRORE");
        }
    }
Per avere una gestione dell'errore devo usare insertOrThrow in luogo di insert.

Ora provo a inserire due records con il nome uguale.
        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Bianchi");
    }
}
11-26 17:55:24.511 31319-31319/? I/System.out: ERRORE

e il database contiene solo il primo record.
Rendere unico il valore di una combinazione di campi:
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE NOMI(_ID INTEGER PRIMARY KEY, " +
                "NOME TEXT, COGNOME TEXT, UNIQUE(NOME, COGNOME))");
    }

    public void save(String nome, String cognome){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME", nome);
        values.put("COGNOME", cognome);
        try {
            db.insertOrThrow("NOMI", null, values);
        }catch(Exception e){
            System.out.println("ERRORE");
        }
    }
Ora riprovo a inserire i due record di prima con solo il nome uguale:
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Bianchi");
    }
}
Nessuna segnalazione di errore:


...e la tabella del database contiene tutti e due i records.

Ora provo a inserire due record uguali per nome e per cognome:
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Rossi");
    }
}
11-26 18:02:17.103 3062-3062/? I/System.out: ERRORE

...e il database contiene solo il primo record.

Ora vorrei un'altra funzione: cercare un record sul database, se presente restituire l'id, se assente inserire il nuovo dato.
Si potrebbe gestire con le funzioni di cui sopra...

mercoledì 26 ottobre 2016

Codici per cancellare un record e per aggiungerlo o modificarlo, basati sulla chiave primaria.

Bene.
Finora mi sono occupato del cancellare di sana pianta tutto il database Firebase e riscriverlo da cellulare, e viceversa.

Ora mi dovrei occupare del modificare singoli record, perché se si modifica un singolo record cancellare e riscrivere tutto il database mi sembra un po' come quello che si compra la macchina nuova perché i posacenere di quella vecchia sono ormai pieni.

Innanzitutto devo cercare sul Firebase il mio record mediante una chiave, e userò la chiave primaria _id.
La ricerca andrà fatta mediante le query di firebase.
Se la ricerca non è vana, ossia se la query dà dei risultati, posso cancellare o modificare il mio singolo record mediante l'apposito codice.
Se ho da aggiungere un record, invece, nel caso la query mi dia ricerca vana devo usare il push().

Posso unire le due cose dicendo "Se c'è modificalo, se non c'è aggiungilo".
Bene: primo compito. Individuare un record e cancellarlo.

Per individuare un record devo creare una query.
Mettiamo che voglio individuare il record n.2
Query query=reference.orderByChild("_id").equalTo(2);
Ora pongo la condizione che esista. Vediamo il numero di children che ha la query:
                Query query=reference.orderByChild("_id").equalTo(2);
                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        System.out.println(dataSnapshot.getChildrenCount());
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
Ci sono riuscito.
Funziona egregiamente.

Ora creo la funzione "cancella" per cancellare selettivamente questo record. Ecco:
                Query query=reference.orderByChild("_id").equalTo(2);
                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        dataSnapshot.getRef().removeValue();
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
Vediamo...

No, non funziona. Il dataSnapshot.getRef() così facendo è ancora relativo alla radice del database.
Provvedo:
                Query query=reference.orderByChild("_id").equalTo(2);
                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot child : dataSnapshot.getChildren()){
                            child.getRef().removeValue();
                        }

                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
E così funziona.

Ora devo provvedere al codice che aggiunge un record al database nel caso questo sia assente.
        bttAggiungiRecord.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Query query=reference.orderByChild("_id").equalTo(2);

                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        if(dataSnapshot.getChildrenCount()==0){
                            Cursor crs=helper.query();
                            crs.moveToFirst();
                            do{
                                if(crs.getInt(0)==2){
                                    PersonaOut p=new PersonaOut(crs.getInt(0),
                                            crs.getString(1),crs.getString(2),
                                            crs.getInt(3),crs.getString(4));
                                    reference.push().setValue(p);
                                }
                            }while(crs.moveToNext());

                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }
        });
Benissimo!
Ora voglio fare che invece se è presente lo modifica.
Ecco, sì:
        bttAggiungiRecord.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                Cursor crs=helper.query();
                crs.moveToFirst();
                do{
                    if(crs.getInt(0)==2){
                        p=new PersonaOut(crs.getInt(0),
                                crs.getString(1),crs.getString(2),
                                crs.getInt(3),crs.getString(4));
                    }
                }while(crs.moveToNext());

                Query query=reference.orderByChild("_id").equalTo(2);
                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        if(dataSnapshot.getChildrenCount()==0){
                            System.out.println(dataSnapshot.getChildrenCount());
                            reference.push().setValue(p);
                        }
                        if(dataSnapshot.getChildrenCount()==1){
                            System.out.println(dataSnapshot.getChildrenCount());
                            for(DataSnapshot child : dataSnapshot.getChildren()){
                                child.getRef().setValue(p);
                            }
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }
        });

martedì 25 ottobre 2016

Esercizi con Firebase e Query

Cerchiamo di mettere a fuoco i problemi di questo database Firebase.
Perché devo ottenere uno snapshot per posizionarmi su un record?
Perché devo posizionarmi su un record?

Perché devo agire su di essi uno per uno. Per dire "cancella un record" devo specificare il record stesso.
Proviamo a cancellare un solo record

reference= FirebaseDatabase.getInstance().getReference();
...
reference.child("-KUw45Hi6pkgGcRzwa7h").removeValue();
e vediamo se funziona.

Certo che funziona! Ma il problema fondamentale è uno solo: io non conosco i nomi dei records, in quanto vengono assegnati dal sistema, e non posso conoscerli in anticipo (oltre al fatto che sono difficili da ricordare).
Dunque devo ricavarli.

Ammettiamo che io debba fare la stessa operazione senza conoscere il nome del record. Come faccio?
L'unica è ottenere uno snapshot del database stesso.
Ci provo, a ottenere un dataSnapshot. Ecco, così ottengo un dataSnapshot che figura fra i parametri.
Un dataSnapshot ha la proprietà getKey() che dovrebbe restituirmi il nome del nodo.
Ma se io faccio così su un dataSnapshot ottenuto dalla radice del database...
                reference.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        System.out.println(dataSnapshot.getKey());
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
...ottengo null.
Però posso ottenere anche i dataSnapshot degli elementi "discendenti" della radice, ossia di ogni figlio di essa, vale a dire di ogni nodo, perché il dataSnapshot ha la proprietà getChildren(), che mi ottiene i dataSnapshot anche degli elementi figli.
                reference.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot child : dataSnapshot.getChildren()){
                            System.out.println(child.getKey());
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
Con questa operazione ottengo i datasnapshot di tutti i figli.
Vediamo se mi dà i nomi "illeggibili".
10-25 16:42:43.585 7992-7992/? I/System.out: -KUwDcEfCy2-QPeLkNFS
10-25 16:42:43.585 7992-7992/? I/System.out: -KUwDcEnBfijEAoDzfkW
10-25 16:42:43.585 7992-7992/? I/System.out: -KUwDcEoAzah1_AhLlxC
Perfetto!
Dunque per cancellare il primo record, facendo così, dobbiamo ottenere il nome del nodo in questo modo.
Però esso viene ottenuto insieme a tutti gli altri... Come fare per isolare quello che ci interessa?

Provo a fare così:
                reference.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        List<String> lista=new ArrayList<String>();
                        for(DataSnapshot child : dataSnapshot.getChildren()){
                            System.out.println(child.getKey());
                            lista.add(child.getKey());
                            
                        }
                        reference.child(lista.get(0)).removeValue();
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
ossia metto i risultati in un'ArrayList e individuo il primo immesso come quello di indice zero.
Vediamo se funziona...

Sì! Funziona!
Ma posso anche usare direttamente il riferimento a un nodo figlio?
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        List<DatabaseReference> lista=new ArrayList<DatabaseReference>();
                        for(DataSnapshot child : dataSnapshot.getChildren()){
                            System.out.println(child.getKey());
                            lista.add(child.getRef());

                        }
                        lista.get(0).removeValue();
                    }
Sì, anche così funziona.
Ma posso usare anche un dataSnapshot di cui prendere il riferimento poi:
                reference.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        List<DataSnapshot> lista=new ArrayList<DataSnapshot>();
                        for(DataSnapshot child : dataSnapshot.getChildren()){
                            System.out.println(child.getKey());
                            lista.add(child);

                        }
                        lista.get(0).getRef().removeValue();
                    }
Quindi, alla fine il removeValue() si applica a un riferimento.
Questo riferimento si può ottenere ottenendo il DataSnapshot da cui ottenere il riferimento mediante getRef(), subito o successivamente, o ottenendo, sempre dal DataSnapshot il Value da cui poi ottenere il riferimento mediante il metodo .child() del riferimento della radice.


Ora, anziché andare per numero di ordine, volendo eliminare il primo (o anche il secondo eccetera) dei records, voglio eliminare solo quello che presenti determinate caratteristiche.
Devo costruirmi una query.

Provo...
Query query=reference.orderByChild("cognome").equalTo("Rossi");
Sono un po' perplesso perché il child di reference è quello identificato dal nome irripetibile... ma forse il "child" di questo caso si riferisce anche ai "nipoti"...
Comunque questa perplessità servirà a farmi ricordare meglio la cosa.
Ora praticamente a una query io posso applicare un ValueEventListener?

Sembrerebbe di sì, e questo significa che ne posso ricavare un dataSnapshot e quindi un insieme di Children...
Vediamo:
        bttRefreshFB.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                reference.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {

                        Query query=reference.orderByChild("cognome").equalTo("Rossi");
                        query.addListenerForSingleValueEvent(new ValueEventListener() {
                            @Override
                            public void onDataChange(DataSnapshot dataSnapshot) {
                                System.out.println(dataSnapshot.getChildrenCount());
                            }

                            @Override
                            public void onCancelled(DatabaseError databaseError) {

                            }
                        });
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });





            }



        });
Ecco: partendo dalla query ho ottenuto un dataSnapshot contenente tutti quei record che hanno un campo "cognome" uguale a "Rossi".
Ottengo, come mi aspettavo un numero di children pari a 1 in quanto solo un record è quello in cui "cognome" è uguale a "Rossi".

Bene, quindi dato che con la query posso selezionare, adesso voglio eliminare i records (ce n'è uno solo) in cui "cognome" equivale a "Minchia".
                reference.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {

                        Query query=reference.orderByChild("cognome").equalTo("Minchia");
                        query.getRef().removeValue();

                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
E invece così facendo ho eliminato tutti i records!
Perché?
Forse perché devo prima ottenere un dataSnapshot.
Ma è proprio indispensabile che ottenga la query nell'amito del metodo addListenerForSingleValueEvent?
No. Query agisce direttamente su reference, e mi seleziona i figli in relazione al criterio proposto.
Riscrivo:
        bttRefreshFB.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Query query=reference.orderByChild("cognome").equalTo("Minchia");
                query.getRef().removeValue();

            }



        });
Provo...

E ottengo lo stesso risultato.
Query è una selezione di un riferimento, quindi va soggetta agli stessi criteri di un riferimento.

        bttRefreshFB.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Query query=reference.orderByChild("cognome").equalTo("Minchia");
                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot child : dataSnapshot.getChildren()){
                            child.getRef().removeValue();
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });

            }



        });


Perfetto!!! Adesso funziona!

Refreshing di tutto il database Firebase

Ripartiamo con i database.
Ho un SQLiteDatabase. Voglio portarlo su Firebase.
        Cursor crs=helper.query();r
        crs.moveToFirst();
        do{
            PersonaOut personaOut=new PersonaOut(crs.getInt(0),crs.getString(1),crs.getString(2),
                    crs.getInt(3),crs.getString(4));
            reference.push().setValue(personaOut);
        }while(crs.moveToNext());
Vediamo se funziona...

Sì, funziona perfettamente.


Ora la situazione è più complessa: vorrei modificare i dati nel database SQLite e trasferire queste modifiche al database online.
Come fare?

Intanto, mi creo un metodo in Helper che consenta di modificare il campo "professione".
    public void update(String cognome,String professione){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("PROFESSIONE",professione);
        db.update("TABELLA",values, "PROFESSIONE=?",new String[]{cognome});
    }
E verifico che funzioni.

Funziona! Per ora ho solo i risultati in locale.
Trasferendoli su Firebase con questo
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Cursor crs=helper.query();
                crs.moveToFirst();
                do{
                    PersonaOut personaOut=new PersonaOut(crs.getInt(0),crs.getString(1),crs.getString(2),
                            crs.getInt(3),crs.getString(4));
                    reference.push().setValue(personaOut);
                }while(crs.moveToNext());
            }
        });
ottengo l'aggiunta dei record a quelli già presenti sul database, con le opportune modifiche.


Ho penato molto per cancellare il database remoto e riscriverlo.
Ho trovato infine la soluzione usando addListenerForSingleValueEvent.
Il problema è che se mettevo un semplice addValueEventListener al reference, ossia al riferimento alla radice del database, una volta settato il listener, la successiva riscrittura di dati, essendo comunque un evento, triggerava il listener, e dato che il codice del listener era quello di cancellare i dati, un attimo dopo questi venivano cancellati.
Usando un listener valido per una sola operazione non ci sono più problemi.
Ora riscrivo il codice per la cancellazione e il refreshing di tutto il database.
        bttRefreshFB.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                reference.addListenerForSingleValueEvent(new ValueEventListener() {

                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot figlio : dataSnapshot.getChildren()){
                            DatabaseReference ref= dataSnapshot.getRef();
                            ref.removeValue();

                        }
                        Cursor crs=helper.query();
                        crs.moveToFirst();
                        do{
                            PersonaOut personaOut=new PersonaOut(crs.getInt(0),crs.getString(1),crs.getString(2),
                                    crs.getInt(3),crs.getString(4));
                            reference.push().setValue(personaOut);
                        }while(crs.moveToNext());

                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });


            }



        });
Ecco, il refreshing funziona.

domenica 23 ottobre 2016

Updating di Firebase.

Bene, ho trovato qualcosa sull'updataggio di Firebase.
E' meglio che non ci mettiamo sbarramenti e andiamo fluidi come l'acqua.
Seguo un codice di StackOverflow...

Intanto inizio dal riferimento al database.
reference= FirebaseDatabase.getInstance().getReference();
Ora faccio riferimento a un record (child del nodo principale), usando il nome del nodo, quello "impronunciabile" attribuito dal sistema:
        reference= FirebaseDatabase.getInstance().getReference();

        DatabaseReference ref= reference.child("-KUae5VW3jfCoWP2yQmc");
        ref.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
E funziona:
10-23 13:10:57.145 19033-19033/? I/System.out: {cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}
Ora, se io cerco nello specifico il nodo che si chiama Rossi, anziché identificarlo per mezzo del codice impronunciabile del nodo?
Usare una query.
Come si usano le query su Firebase?

Ecco, io voglio cercare Rossi, non Hxydkjlsdknsokdgrunth eccetera: come devo fare?
Ci provo...

Ottengo la reference al database:
reference= FirebaseDatabase.getInstance().getReference();
Ora, nella situazione di prima io selezionavo il nodo con il nome impronunciabile facendo così:
DatabaseReference ref= reference.child("-KUae5VW3jfCoWP2yQmc");
mentre ora provo questo:
        reference= FirebaseDatabase.getInstance().getReference();
        reference.orderByChild("cognome").equalTo("Rossi").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-23 18:02:42.041 26561-26561/? I/System.out: {-KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}}
Perfetto! Ora provo a eliminare la condizione equalTo:
         reference.orderByChild("cognome").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-23 18:13:41.641 28531-28531/? I/System.out: {-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}, -KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}, -KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}}
Ecco: ottengo tutti.

Ma se faccio OrderByChild("nome"):
        reference.orderByChild("nome").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-23 18:20:05.071 29020-29020/? I/System.out: {-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}, -KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}, -KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}}
L'ordine è sempre lo stesso.

Ora voglio selezionare quello che si chiama Luigi di nome:
        reference.orderByChild("nome").equalTo("Luigi").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-23 18:27:48.931 31604-31604/? I/System.out: {-KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}}
Perfetto!

Adesso voglio trovare quel simpatico galantuomo che fa di professione lo squartatore:
        reference.orderByChild("professione").equalTo("Squartatore").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-23 18:29:49.581 32582-32582/? I/System.out: {-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}}


Perfetto!
Ora faccio cambiare mestiere a un altro galantuomo per vedere se con la query "Squartatore" me li identifica tutti e due. Ecco, Mario Rossi non fa più lo stupratore ma fa lo squartatore anche lui.
Vediamo...

Non c'è stato bisogno di far correre il programma ancora: dato che il cambiamento del database mi attiva il listener ho ottenuto direttamente sulla finestra LogCat questo:
10-23 18:32:10.361 32582-32582/? I/System.out: {-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}, -KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Squartatore, nome=Mario, eta=45}}
Perfetto!
Ora vediamo: esiste un "tipo" Query che identifichi questi gruppi selezionati per mezzo di query?
        Query query= reference.orderByChild("professione").equalTo("Squartatore");
        
        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
Sì, esiste. Posso immagazzinare alcune queries in modo da avere la selezione già preconfezionata, forse...

Ora Mario Rossi ha messo la testa a posto e ha cambiato vita, e non fa più lo stupratore o lo squartatore, ma adesso fa il chierichetto presso la parrocchia.
Come farlo sapere anche a Firebase?

Vediamo...

Vado sul nodo:
        reference= FirebaseDatabase.getInstance().getReference();
        DatabaseReference ref=reference.child("-KUae5VW3jfCoWP2yQmc");
Quindi creo una HashMap, e ci metto "professione" e "Chierichetto".
Quindi applico al nodo in cui mi trovo il metodo updateChildren().
        Map<String,Object> mappa=new HashMap<String,Object>();
        mappa.put("professione","Chierichetto");
        ref.updateChildren(mappa);
E funziona!



Ma ho identificato il galantuomo in questione con il nome impronunciabile del nodo.
Ora lo identifico in modo più umano.
Faccio una query con il cognome.
Anche Luigi Verdi ha cambiato vita, e adesso non fa più il serial killer ma il sagrestano.
Identifichiamolo con il nome.

Ho avuto molta difficoltà a individuare il nome del nodo dallo snapshot. Ecco:
        Query query= reference.orderByChild("cognome").equalTo("Verdi");

        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

                for(DataSnapshot child : dataSnapshot.getChildren()){
                    DatabaseReference r=reference.child(child.getKey());
                    Map<String,Object> mappa=new HashMap<String, Object>();
                    mappa.put("professione","sagrestano");
                    r.updateChildren(mappa);
                }

            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
E finalmente Verdi fa il sagrestano!



Anche l'altro galantuomo, Carlo Bianchi, rispettabile squartatore, ha deciso di mettere la testa a posto, e si fa addirittura frate! Dunque cambia anche nome: prenderà il nome di Fra' Mamozzio.
Vediamo se riusciamo a farlo sapere a Firebase...
        Query query= reference.orderByChild("cognome").equalTo("Bianchi");

        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for(DataSnapshot record : dataSnapshot.getChildren()){
                    DatabaseReference r = reference.child(record.getKey());
                    Map<String,Object> mappa = new HashMap<String, Object>();
                    mappa.put("professione","frate");
                    mappa.put("nome","Fra Mamozzio");
                    r.updateChildren(mappa);
                    

                }


            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
Questa volta abbiamo corretto due campi. Vediamo se funziona...



Perfetto!!!

Pensieri sparsi sulla struttura di JSON

E' necessario che giri un po' intorno a questo database Firebase...

Ho usato la sintassi con getValue() direttamente in una classe (che dovrebbe essere denominata POJO, in questo caso)...
Riprovo a ricostruire il codice. Con questo, io prendo riferimento al primo nodo del database:
reference= FirebaseDatabase.getInstance().getReference();
Ora mi esploro questo nodo di base.
        reference= FirebaseDatabase.getInstance().getReference();

        reference.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println(dataSnapshot.getValue());
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-22 13:32:17.125 3352-3352/? I/System.out: {-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}, -KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}, -KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}}
Ecco, ho ottenuto tutto il mio piccolo database di soggetti infami recuperando il nodo radice.
Vediamo meglio come sta messo...
{
-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}, 
-KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}, 
-KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}
}
E andiamoci a studiare la struttura del JSON.

Confrontiamo con un esempio preso da un tutorial:
var JSON = {
  proprieta1: 'Valore',
  proprieta2: 'Valore',
  proprietaN: 'Valore'
}
e mi coloro quell'altro in modo analogo:
reference= {
-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}, 
-KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}, 
-KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}
}
Ecco, ogni nodo contiene un "array" di nodi ognuno dei quali è composto di due parti, che si chiamano nome e valore.

Bene, una volta visto questo, cambio discorso e vado a vedere che succede se devo updatare un record.
Posso ottenere, con la sintassi vista prima, in cui leggo il contenuto del nodo principale, l'elenco dei children di questo nodo?
Vediamo:
        reference.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot child : dataSnapshot.getChildren()) {
                    System.out.println(child.getValue());
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
10-22 14:57:18.235 10386-10386/? I/System.out: {cognome=Rossi, professione=Stupratore, nome=Mario, eta=45}
10-22 14:57:18.235 10386-10386/? I/System.out: {cognome=Bianchi, professione=Squartatore, nome=Carlo, eta=54}
10-22 14:57:18.235 10386-10386/? I/System.out: {cognome=Verdi, professione=Serial killer, nome=Luigi, eta=36}
Come avevo già, del resto, fatto.
Ma se io updato uno dei tre, ammettiamo che Luigi Verdi si sia ravveduto e adesso faccia un lavoro onesto, come faccio?
Come si fa una query su database Firebase?

sabato 22 ottobre 2016

Leggere i dati dal database Firebase in una classe predisposta.

E poi sono arrivato anche a leggere i dati dal database con questo codice:
public class MainActivity extends AppCompatActivity {

    Persona persona;
    DatabaseReference nuovo;
    Helper helper;
    Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        final DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();

        button=(Button)findViewById(R.id.button2);




        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                databaseReference.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot record : dataSnapshot.getChildren()){
                           Persona p=record.getValue(Persona.class);
                            System.out.println("NOME "+p.nome+" - COGNOME "+p.cognome+" - PROFESSIONE "+p.professione);

                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }
        });
    }

}

class Persona{
    public String nome;
    public String cognome;
    public int eta;
    public String professione;
    public Persona(){}
    public Persona(String nome, String cognome, int eta, String professione){
        this.nome=nome;
        this.cognome=cognome;
        this.eta=eta;
        this.professione=professione;
    }
}

class Helper extends SQLiteOpenHelper{

    Context context;
    public Helper(Context context) {
        super(context, "mioDatabase.db", null, 1);
        this.context=context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE TABELLA(NOME TEXT, COGNOME TEXT, ETA INTEGER, PROFESSIONE TEXT)");
    }

    public void save(Persona p){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME",p.nome);
        values.put("COGNOME",p.cognome);
        values.put("ETA",p.eta);
        values.put("PROFESSIONE",p.professione);
        db.insert("TABELLA",null,values);
    }

    public Cursor query(){
        SQLiteDatabase db=this.getWritableDatabase();
        Cursor crs=db.rawQuery("SELECT* FROM TABELLA",null);
        crs.moveToFirst();
        return crs;
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
Mi salvo il codice qui

venerdì 21 ottobre 2016

Leggere i dati in una istanza di classe dal database Firebase.

Ora mi devo occupare della parte inversa, ossia leggere i dati dal database remoto e portarli, per mezzo di una classe consona, su un database SQLite sul cellulare.
L'ultima parte si farà con il metodo helper.save(...) avente per parametro la classe stessa.
Il problema sta nella lettura dei dati.
Vediamo...

Ecco: bisogna aggiungere un listener (ho subordinato la lettura dei dati alla pressione su un button).
         button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                databaseReference.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        System.out.println(dataSnapshot.getValue());
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }
        });
10-21 12:30:54.625 10720-10720/? I/System.out: {-KUae5VrVyXMk2lFt39t={cognome=Bianchi, professione=Ladro, nome=Carlo, eta=54}, -KUae5VW3jfCoWP2yQmc={cognome=Rossi, professione=Scassinatore, nome=Mario, eta=45}, -KUae5VsYGesGotl6PZf={cognome=Verdi, professione=Falsario, nome=Luigi, eta=36}}
Vediamo se riesco a essere un po' più specifico: qui ho aggiunto il listener a tutto il database.
Provo ad aggiungerlo a un singolo nodo...

La via migliore che ho trovato, non avendo predeterminati i nomi dei nodi, né potendovi costruire agevolmente un ciclo for...next, è quella di prendermi tutto il DataSnapshot, come da tutorial trovati, e separarlo nei singoli nodi mediante il getChildren().
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                databaseReference.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot record : dataSnapshot.getChildren()){
                            System.out.println(record.getValue());
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }
        });
Ed eccoli in sequenza:
10-21 13:00:57.645 15520-15520/? I/System.out: {cognome=Rossi, professione=Scassinatore, nome=Mario, eta=45}
10-21 13:00:57.645 15520-15520/? I/System.out: {cognome=Bianchi, professione=Ladro, nome=Carlo, eta=54}
10-21 13:00:57.645 15520-15520/? I/System.out: {cognome=Verdi, professione=Falsario, nome=Luigi, eta=36}

Ora devo solo vedere come convertire questi in istanze della classe Persona, da poter dare in bocca al mio metodo helper.save come parametri, e il gioco è fatto...

Dopo essere ammattito un po', ho trovato quello che serve a me.
E' più semplice di quello che sembrava:
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                databaseReference.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for(DataSnapshot record : dataSnapshot.getChildren()){
                           Persona p=record.getValue(Persona.class);
                            System.out.println("NOME "+p.nome+" - COGNOME "+p.cognome+" - PROFESSIONE "+p.professione);

                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
            }
        });
E ottengo l'immediato cambiamento sul dispositivo quando vado a cambiare qualcosa nel database remoto.

Perfetto!

Primo trasferimento di SQLiteDatabase su Firebase!

Per ripasso

Codice per rinominare la "radice" del database:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DatabaseReference databaseReference=FirebaseDatabase.getInstance().getReference();
        databaseReference.setValue("Ciao, ciccio");


    }
Bene.
Ora provo, come da esempio a mettere una classe su un database.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        
        Persona persona=new Persona("Mario","Rossi");

        DatabaseReference databaseReference= FirebaseDatabase.getInstance().getReference();
        databaseReference.setValue(persona);




    }

    class Persona{
        public String nome;
        public String cognome;

        public Persona(String nome, String cognome){
            this.nome=nome;
            this.cognome=cognome;
        }
    }
Ho usato il metodo setValue del DatabaseReference.
Ma leggo che si può usare anche il metodo push.
Vediamolo...

Ecco sì: questo è perfetto:
public class MainActivity extends AppCompatActivity {

    Persona persona;
    DatabaseReference nuovo;

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


        DatabaseReference databaseReference= FirebaseDatabase.getInstance().getReference();
        persona=new Persona("Mario","Rossi");
        nuovo=databaseReference.push();
        nuovo.setValue(persona);
        persona=new Persona("Giuseppe","Verdi");
        nuovo=databaseReference.push();
        nuovo.setValue(persona);
        persona=new Persona("Antonello", "Il Pazzo");
        nuovo=databaseReference.push();
        nuovo.setValue(persona);





    }

    class Persona{
        public String nome;
        public String cognome;

        public Persona(String nome, String cognome){
            this.nome=nome;
            this.cognome=cognome;
        }
    }


}


Ora mi creo un semplice database SQLite e tramite una classe intermedia lo salvo in remoto.
Prima mi compilo il database SQLite sul cellulare.
public class MainActivity extends AppCompatActivity {

    Persona persona;
    DatabaseReference nuovo;
    Helper helper;
    Button button;
    Button buttonSave;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        //DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();

        button=(Button)findViewById(R.id.button2);
        buttonSave=(Button)findViewById(R.id.button3);



        buttonSave.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                persona=new Persona("Mario","Rossi",45,"Scassinatore");
                helper.save(persona);
                persona=new Persona("Carlo","Bianchi",54,"Ladro");
                helper.save(persona);
                persona=new Persona("Luigi","Verdi",36, "Falsario");
                helper.save(persona);
            }
        });
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Cursor c=helper.query();
                do{
                    System.out.println(c.getString(0)+" "+c.getString(1)+" "+c.getInt(2)+" "+c.getString(3));
                } while(c.moveToNext());
            }
        });
    }

}

class Persona{
    public String nome;
    public String cognome;
    public int eta;
    public String professione;
    public Persona(String nome, String cognome, int eta, String professione){
        this.nome=nome;
        this.cognome=cognome;
        this.eta=eta;
        this.professione=professione;
    }
}

class Helper extends SQLiteOpenHelper{

    Context context;
    public Helper(Context context) {
        super(context, "mioDatabase.db", null, 1);
        this.context=context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE TABELLA(NOME TEXT, COGNOME TEXT, ETA INTEGER, PROFESSIONE TEXT)");
    }

    public void save(Persona p){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME",p.nome);
        values.put("COGNOME",p.cognome);
        values.put("ETA",p.eta);
        values.put("PROFESSIONE",p.professione);
        db.insert("TABELLA",null,values);
    }

    public Cursor query(){
        SQLiteDatabase db=this.getWritableDatabase();
        Cursor crs=db.rawQuery("SELECT* FROM TABELLA",null);
        crs.moveToFirst();
        return crs;
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
(qui ho commentizzato il codice relativo a Firebase perché adesso devo solo creare un database, il codice cambierà in seguito).

Perfetto: il database è stato creato e compilato correttamente, ne ho la conferma:
10-21 11:40:44.305 4257-4257/? I/System.out: Mario Rossi 45 Scassinatore
10-21 11:40:44.305 4257-4257/? I/System.out: Carlo Bianchi 54 Ladro
10-21 11:40:44.305 4257-4257/? I/System.out: Luigi Verdi 36 Falsario
Ora viene la parte "tosta"!

Devo prendere un Cursor dal mio database.
Per ogni RECORD devo fare un push e quindi un setValue, ossia aggiungere un NODO al database.
Quindi scorrere il Cursor, porre i valori in un'istanza della classe Persona e quindi fare push e settare Value all'istanza di Persona.
Traduciamo in codice l'idea...

        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Cursor c=helper.query();
                do{
                   persona=new Persona(c.getString(0),c.getString(1),c.getInt(2),c.getString(3));
                } while(c.moveToNext());
            }
        });
Ho creato un'istanza di Persona mettendo i valori dei campi come parametri del costruttore della classe.
Ora...
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Cursor c=helper.query();
                do{
                   persona=new Persona(c.getString(0),c.getString(1),c.getInt(2),c.getString(3));
                    DatabaseReference nodo=databaseReference.push();
                    nodo.setValue(persona);

                } while(c.moveToNext());
            }
        });
...questo dovrebbe funzionare. Vediamo...

ALLA GRANDE!!!

martedì 26 gennaio 2016

Salvataggio nel database di un'immagine sotto forma di Blob

Una possibile soluzione: memorizzare direttamente immagini in un database, opportunamente già scalate.

Come si fa a memorizzare un'immagine in un database? Lo avevo visto ma non lo ricordo...

Che tipo di dati è Blob?

Per memorizzare un'immagine come dati blob bisognerebbe convertirla in array di bytes.

Il metodo compress su una bitmap, cosa è?
Troviamolo...

Scrive la bitmap su un outputstream.
Questo può essere convertito in ByteArray.

link.

Realizzati tutti e due i codici, per l'immissione e per la lettura dell'immagine da database:

metodo di helper:
  public void save(int drawable){
   SQLiteDatabase db=this.getWritableDatabase();
   Bitmap bmp=BitmapFactory.decodeResource(getResources(), drawable);
   //dovrei creare un outputstream...
   ByteArrayOutputStream stream=new ByteArrayOutputStream();
   //ora usare il metodo compress sulla bitmap...
   bmp.compress(CompressFormat.JPEG, 50, stream);
   byte[] blob=stream.toByteArray();
   ContentValues values=new ContentValues();
   values.put("IMMAGINE", blob);
   db.insert("TABELLA", null, values);
   
  }


Dal metodo query() di helper ottengo il cursore, che gestisco così:
  byte[] blob=helper.query().getBlob(1);
  Bitmap bmp=BitmapFactory.decodeByteArray(blob, 0, blob.length);

sabato 23 gennaio 2016

Ripasso delle modifiche del CursorAdapter per la AutoCompleteTextView.

Ora devo ricordare quelle due funzioni che servono per accoppiare un database a una AutoCompleteTextView.
Vediamo un po'...

  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   
   return null;
   
  }
Cosa restituisce questo? Un cursor nel caso in cui la stringa digitata corrisponda alla parte iniziale di alcuni campi.
Come si riempiva? Non lo ricordo...

Ci provo.
Intanto mi creo l'AutoCompleteTextView.
E la istanzio da codice java.

Ecco alla fine ricostruite le due funzioni che ho appreso recentemente e che avevo in parte dimenticato:
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint!="") return helper.query(constraint+"");
   return null;
   
  }
  
  @Override
  public CharSequence convertToString(Cursor c){
   return c.getString(1);
   
  }
con le quali la autoComplete funziona alla perfezione! L'adapter, aggiustato per la autoComplete, è:
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line, null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(1));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint!="") return helper.query(constraint+"");
   return null;
   
  }
  
  @Override
  public CharSequence convertToString(Cursor c){
   return c.getString(1);
   
  }
  
 }

Ripasso del LIKE nella query di selezione di un database.

Bene.
Adesso l'esercizio consiste nel caricare immagini su una GridView ordinabile per categorie.

Per prima cosa creo un database con una tabella Categorie, la quale ha due campi, Categoria e Path.
Questa tabella è collegata con una AutoComplete.
Ho problemi a ricordare con certezza la sintassi della query con il LIKE.
Apro un sottoprogramma di ripasso...

Questo è il codice della query che ho scritto:
  public Cursor query(String str){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TblCategorie where categoria like '" +str+"%'", null);
   return crs;
  }
e che devo verificare per essere sicuro che sia scritto bene.
Dunque mi predispongo una ListView in cui scaricare e visualizzare tutti i record del database.
Ecco: ho inserito due records in cui i valori del campo "categoria" erano "cazzate" e "minchiate", e constato che il funzionamento è quello che mi aspettavo:
codice completo:
public class MainActivity extends Activity {
 
 private ListView listView;
 private Helper helper;
 private Adapter adapter;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  helper=new Helper(this);
  listView=(ListView)findViewById(R.id.listView1);
  helper.save("cazzate","unCertoPath");
  helper.save("minchiate","unCertoPath");
  adapter=new Adapter(this,helper.query("m"));
  listView.setAdapter(adapter);
  
 }
 
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   View v=inflater.inflate(R.layout.hills, null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   TextView testo=(TextView)view.findViewById(R.id.textView1);
   testo.setText(cursor.getString(1));
   
  }
  
 }
 
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "mioDatabase", null, 1);
   // TODO Auto-generated constructor stub
  }

  public void save(String categoria,String path){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("categoria",categoria);
   db.insert("TblCategorie", null, values);
  }
  
  public Cursor query(String str){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TblCategorie where categoria like '" +str+"%'", null);
   return crs;
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table tblCategorie(_id integer primary key, categoria text,path text)");
   
   
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
 }
Se la parte marcata in rosso ha come parametro una lettera (in questo caso la "m") appaiono solo i records in cui "categoria" inizia per "m", se la "c" solo quelli in cui "categoria" inizia per "c", se si tratta di una stringa vuota appaiono tutti i records, se è uno spazio " " non appare nessun record.
Ottimo ripasso!

domenica 17 gennaio 2016

Ottenere il popolamento della lista di suggerimenti di una AutoCompleteTextView direttamente da un database

setFilterQueryProvider.
dovrebbe eseguire la query con le limitazioni date da constraint.
Restituisce getCursor(constraint).
Il problema è che getCursor non ce l'abbiamo.
E' una funzione che deve essere creata, ed è qui che non ci capisco niente.

Ogni volta che cambia il contenuto della textview scatta un metodo che mi mostra la stringa constraint.
Cerchiamo di riprodurla.

Non mi è chiaro se la view che è stata inflatata nel metodo newView deve essere... Se è una TextView, non si fa come si è fatto nell'inflatare una view esterna, ossia cercare dentro di essa una TextView, ma se ne imposta direttamente il testo prendendo il valore da una colonna del Cursor.

 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line,null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
 }
Ecco: così, nel momento in cui viene digitato un testo nella autoComplete, la lista viene popolata di TUTTI i valori presenti nella colonna 0 del Cursor.
Invece dobbiamo fare in modo che venga popolata soltanto di quelli che iniziano con i caratteri digitati. E' quello il problema!

E abbiamo conseguito una prima acquisizione.

Ora, c'è un evento che si verifica a ogni digitazione di testo nella autoComplete.
Cerco di ricordarlo e di catturarlo.
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   Log.d("CONSTRAINT",""+constraint);
   return null;
   
  }
01-17 15:21:07.555: D/CONSTRAINT(2233): null
01-17 15:21:08.945: D/CONSTRAINT(2233): ci
01-17 15:21:10.225: D/CONSTRAINT(2233): cia
01-17 15:21:11.756: D/CONSTRAINT(2233): ciao
Ecco: quando la lunghezza della stringa è di due o più caratteri, questa funzione viene eseguita e su LogCat mi appaiono i caratteri digitati.
Ora devo praticamente elaborare qualcosa che da un Cursor, ossia da una serie di records, riconosca l'inizio della stringa e mi scriva i valori che iniziano con quell'inizio.

E finalmente ci sono riuscito!!!

Ho modificato la query in questo senso (codice dell'helper):
  public Cursor query(String args){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select categoria as _id from TabCat where categoria like '%"+args+"%' ", null);
   crs.moveToFirst();
   return crs;
  }
e poi ho aggiunto, nel codice del CursorAdapter:
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   Log.d("CONSTRAINT",""+constraint);
   if(constraint==null){
    return helper.query(" ");
   } else{
    return helper.query(""+constraint);
   } 
  }
Ecco, adesso ottengo una lista adeguata per la mia AutoCompleteTextView!
Codice completo con classe che estende SQLiteOpenHelper per la creazione del database e classe che estende CursorAdapter:
public class MainActivity extends Activity {
 
 Helper helper;
 private AutoCompleteTextView autoComplete;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  autoComplete=(AutoCompleteTextView)findViewById(R.id.autoCompleteTextView1);
  helper=new Helper(this);
  helper.save("foto");
  helper.save("cartoni");
  helper.save("mostri");
  
  
  Adapter adapter=new Adapter(this,helper.query(""));
   
 
  
  autoComplete.setAdapter(adapter);
 }
 
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line,null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint==null){
    return helper.query(" ");
   } else{
    return helper.query(""+constraint);
   } 
  }

  
 }
 
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "mioDatabase", null, 1);
   
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table TabCat(categoria text unique, path text )");
   
  }
  
  public void save(String categoria){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("categoria", categoria);
   values.put("path", "path");
   db.insert("TabCat", null, values);
  }
  
  public Cursor query(String args){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select categoria as _id from TabCat where categoria like '%"+args+"%' ", null);
   crs.moveToFirst();
   return crs;
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
  
 }
C'è un problema.
Come faccio a ottenere nella autoComplete il testo?
Il metodo runQueryOnBackgroundThread mi restituisce un cursor.
I suggerimenti mi risultano congruenti.
Ma poi, quando vado a cliccare sul suggerimento, ottengo una strana formula nella autoComplete al posto della stringa presente come suggerimento.
Perché?

Probabilmente perché, per qualche motivo che non riesco ancora a mettere a fuoco, viene scelta un'altra colonna del cursore per riempire la TextView una volta che sia stata selezionata la voce.
Con un po' di intuito, seguendo questo link, ho sistemato le cose aggiungendo al CursorAdapter questa funzione:
  @Override
  public String convertToString(Cursor cursor) { 
   return cursor.getString(0); 
  } 
e tutto sembra risolto!

Tentativo di CursorAdapter con AutoCompleteTextView

runQueryOnBackgroundThread sarebbe un metodo che scatterebbe quando si modifica il testo della AutoCompleteTextView.
Cerco ulteriori conferme a ciò.
Ecco: Io inserisco nella classe che estende CursorAdapter il metodo runQueryOnBackGroundThread e non ottengo più i suggerimenti della AutoCompleteTextView:
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);


  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent,false);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   return null;
 
        }
 }
E...



Adesso la tolgo:
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);


  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent,false);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
  /*@Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   return null;
 
         }*/
 }

e...



Dunque la chiave è in questo metodo: bisogna riempirlo in modo tale che espanda i suggerimenti in modo filtrato.
E come?



Basta! Sono arrivato a capire che c'è un metodo che viene chiamato quando cambia il testo e prende come parametro la stringa presente nella AutoComplete, ma poi come faccia a selezionare i records del database in base a questo, non lo so.
Ulteriori tentativi in seguito. Meglio allentare, per il momento.

venerdì 15 gennaio 2016

Sperimentando queries su Android.

Realizzare un database con due tavole: una con tre campi e una con due campi con un campo dallo stesso nome.
Fatto. Ambedue con tre campi di cui un _id e altri due...

Adesso devo costruire una query JOIN.
Vediamo se mi riesce...

Intanto faccio delle prove in modo graduale.
Seleziono alcuni campi di una tabella invece di selezionarli tutti. Non vi figura il campo _id
Ogni volta che tento di inserire un valore in un campo UNIQUE, se il valore è già presente, ottengo un messaggio di errore con "Is not unique", ma non si tratta di un errore fatale.
Ottengo poi un messaggio di errore fatale, con crash dell'applicazione, quando invece il CursorAdapter non trova il campo _id in quanto questo è omesso dalla selezione nella query.
Tolgo la condizione UNIQUE per evitare di ottenere quei messaggi di errore che mi rendono più difficile la lettura del LogCat, e faccio ripartire il programma. Strano che gli errori "is not unique" mi figurano ugualmente.
Tralascio almeno al momento questo particolare, e trovo l'errore FATALE:
01-12 22:23:02.175: E/AndroidRuntime(26294): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.lab10/com.example.lab10.MainActivity}: java.lang.IllegalArgumentException: column '_id' does not exist
Ora applico quel trucco dell'aggiungere a uno dei campi selezionati "AS _id", e vediamo:
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT NOME AS _id,CATEGORIA FROM TABELLA", null);
			return crs;
		}
E vediamo se ottengo ancora errori fatali...

No! Non ho trovato nessun messaggio di errore! Dunque, se voglio selezionare campi in cui non vi sia compreso _id, o se la tabella semplicemente non possiede un campo chiamato _id, basta aggiungere quelle due paroline e il problema è risolto! Ora, dato che il mio codice inserisce valori anche in un'altra tabella, provo a selezionare da tabelle multiple!

Creo campi e record come li ho creati in quel mio piccolo esercizio con Access...

Una tabella Padri e una tabella Figli, la prima con i campi id e nome, la seconda con i campi extId e nome. Ne do qui una rappresentazione:
IDNome
1Mario
2Antonio
3Giuseppe
4Luigi


IDextIdNome
11Pierino
22Carletto
31Gigetto
43Paolino
12Checco
22Dadino
31Lallo
43Bobo


Creo la tabella:
		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL("CREATE TABLE TABELLA(_id INTEGER PRIMARY KEY, NOME TEXT)");
			db.execSQL("CREATE TABLE TAB2(_id INTEGER PRIMARY KEY, ExtID INTEGER, NOME TEXT)");
		}
E la riempio...

Ho creato due metodi per il riempimento della prima e della seconda tabella.
		public void save(String nome){
			SQLiteDatabase db=this.getWritableDatabase();
			ContentValues values=new ContentValues();
			values.put("NOME", nome);
			db.insert("TABELLA", null, values);
			
		}
		
		public void save2(int extId, String nome){
			SQLiteDatabase db=this.getWritableDatabase();
			ContentValues values=new ContentValues();
			values.put("ExtId",extId);
			values.put("NOME", nome);
			db.insert("TAB2", null, values);
			
		}
Ora mi esercito a creare diversi tipi di query, mediante l'uso del SQL.

Ecco una query, che ho creato dopo aver sperimentato query più semplici che non è il caso di riportare, perché fatte solo per verificare che non commettessi errori di sintassi.
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT TABELLA.NOME AS _id,TAB2.NOME FROM TABELLA,TAB2 WHERE TABELLA._ID = TAB2.EXTID", null);
			return crs;
		}
Ora provo a ottenere lo stesso risultato con il JOIN.
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT TABELLA.NOME AS _id,TAB2.NOME FROM TABELLA INNER JOIN TAB2 ON TABELLA._ID = TAB2.EXTID", null);
			return crs;
		}




Bene.
Il risultato è quello voluto ad eccezione dell'ordine.
Come si fa a ordinare in ordine alfabetico i records?

Ma sì: con la clausola ORDER BY:
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT TABELLA.NOME AS _id,TAB2.NOME FROM TABELLA INNER JOIN TAB2 ON TABELLA._ID = TAB2.EXTID ORDER BY TABELLA.NOME", null);
			return crs;
		}
Ed ecco:



...e non ci sono problemi!

mercoledì 13 gennaio 2016

Rispolverando reminiscenze di SQL con Access. Corrispondenza uno a molti fra tabelle

Tabelle di database.
Ricominciamo dalle basi elementari del discorso...

Corrispondenza uno a molti, corrispondenza molti a molti.
Facciamoci un esempio pratico di corrispondenza uno a molti: un padre che ha più figli. Un padre può avere più figli, ma un figlio può avere un solo padre.
Costruiamo una tabella o due tabelle...

id Nome
1 Mario
2 Antonio
3 Giuseppe
4 Luigi
id Nome extId
1 Pierino 1
2 Carletto2
3 Gigetto 1
4 Paolino 3
5 Checco 2
6 Dadino 2
7 Lallo 1
8 Bobo 3
L'extID della seconda tabella corrisponde all'ID della prima tabella.
E dunque creiamo la query che fornisce i risultati dell'unione delle tabelle:
SELECT Padri.Nome, Figli.Nome
FROM Padri INNER JOIN Figli ON Padri.ID=Figli.ExtID;
Ed ecco la tabella che mi viene restituita:
Padri.Nome Figli.Nome
Mario         Pierino
Mario         Gigetto
Mario         Lallo
Antonio         Carletto
Antonio         Checco
Antonio         Dadino
Giuseppe Paolino
Giuseppe Bobo
Ma nel compilare la seconda tabella mi ero dimenticato che la prima comprendesse quattro records e non tre.
Così ho dimenticato dl attribuire figli al quarto genitore.
La cosa mi torna utile per insegnare a me stesso la differenza fra INNER JOIN, che ho usato in questa query, e LEFT JOIN, ipotizzando che Luigi sia un uomo che non ha ancora avuto figli.
E adesso sostituisco nella query INNER JOIN con LEFT JOIN e mi compaiono i record non "appaiati" della prima tabella.
SELECT Padri.Nome, Figli.Nome
FROM Padri LEFT JOIN Figli ON Padri.ID=Figli.ExtID;
Vediamo...
Padri.Nome Figli.Nome
Mario         Pierino
Mario         Gigetto
Mario         Lallo
Antonio         Carletto
Antonio         Checco
Antonio         Dadino
Giuseppe Paolino
Giuseppe Bobo
Luigi ott
Ecco qua.
Da dove dipende che una tabella sia considerata Left o Right?
Ecco: se inverto l'ordine delle tabelle a cavallo dell'istruzione LEFT JOIN ottengo un'inversione del ruolo di Left e Right fra le due.
Anziché
SELECT Padri.Nome, Figli.Nome
FROM Padri LEFT JOIN Figli ON Padri.ID=Figli.ExtID;
scrivo, invertendo:
SELECT Padri.Nome, Figli.Nome
FROM Figli LEFT JOIN Padri ON Padri.ID=Figli.ExtID;
e ottengo questa tabella:
Padri.Nome Figli.Nome
Mario         Pierino
Mario         Gigetto
Mario         Lallo
Antonio         Carletto
Antonio         Checco
Antonio         Dadino
Giuseppe Paolino
Giuseppe Bobo
Ma se adesso pongo un RIGHT JOIN cosa accadrà? Prevedibile...

SELECT Padri.Nome, Figli.Nome
FROM Figli RIGHT JOIN Padri ON Padri.ID=Figli.ExtID;
No...prevedibile un corno!
Padri.Nome Figli.Nome
Mario Pierino
Mario Gigetto
Mario Lallo
Antonio Carletto
Antonio Checco
Antonio Dadino
Giuseppe Paolino
Giuseppe Bobo
Luigi 
Mi viene esattamente come prima...

...è necessario che metta un po' di ordine fra i concetti di sinistra e destra...

lunedì 11 gennaio 2016

Ripasso dei Database SQL e creazione di più tabelle sul database.


Stavo pensando che forse potrei semplicemente, anziché usare onItemClickListener sulla AutoCompleteTextView, creare una tabella del database con una chiave a chiave unica... ma il rischio è che l'utente digiti una categoria in modo sbagliato e il programma la inserisca come categoria diversa quando nelle intenzioni dell'utente avrebbe dovuto essere una categoria già esistente.
Meglio mantenere la AutoComplete, ma potrei anche mettere la chiave unica...
Per il momento, cerchiamo di rimettere le mani sul database creando un'altra tabella.
Per fare questo, devo crearmi un'esercitazione sui database.

public class MainActivity extends Activity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  DBHelper helper=new DBHelper(this);
  helper.save("Mario", "Deficiente");
  Log.d("RECORD",helper.query().getString(1)+" "+helper.query().getString(2));
 }
 
 class DBHelper extends SQLiteOpenHelper{

  public DBHelper(Context context) {
   super(context, "mioDatabase", null, 1);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABLE TABELLA(_ID INTEGER PRIMARY KEY, NOME TEXT, CATEGORIA TEXT)");
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
  public void save(String nome, String categoria){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("NOME", nome);
   values.put("CATEGORIA", categoria);
   db.insert("TABELLA", null, values);
   
  }
  
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("SELECT * FROM TABELLA", null);
   crs.moveToFirst();
   return crs;
  }
  
 }
...con tanto di verifica in LogCat. Ripetuto più volte.
Adesso devo creare un'altra tabella...

Finalmente ci sono riuscito! Ho avuto grossi problemi con la gestione di ContentValues fino a quando ho trovato il metodo clear() che ha risolto la situazione:
public class MainActivity extends Activity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  DBHelper helper=new DBHelper(this);
  helper.save("Antonello","Scemo");
  Log.d("RISULTATO",helper.query().getString(1));
 }
 
 class DBHelper extends SQLiteOpenHelper{

  public DBHelper(Context context) {
   super(context, "mioDatabase", null, 1);
  
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABLE TABELLA(_ID INTEGER PRIMARY KEY, NOME TEXT)");
   db.execSQL("CREATE TABLE TABELLA2(_ID INTEGER PRIMARY KEY, CATEGORIA TEXT)");
   
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
  public void save(String nome, String categoria){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("NOME", nome);
   db.insert("TABELLA", null, values);
   values.clear();
   values.put("CATEGORIA", categoria);
   db.insert("TABELLA2", null, values);
  }
  
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("SELECT * FROM TABELLA2", null);
   crs.moveToFirst();
   return crs;
  }
  
 }
Ed ecco il risultato:
01-10 22:32:49.126: D/dalvikvm(16447): GC_CONCURRENT freed 0K, 1% free 14249K/14368K, paused 4ms+1ms, total 7ms
01-10 22:32:49.686: D/RISULTATO(16447): Scemo
01-10 22:32:49.786: D/gralloc_goldfish(16447): Emulator without GPU emulation detected.
01-10 22:32:49.876: I/ActivityManager(1224): Displayed com.example.lab10/.MainActivity: +1s257ms (total +20m1s538ms)
il dato preso dalla seconda tabella.

Ora potrei mettermi a giocherellare con le istruzioni SQL per maneggiare le tabelle...