JavascriptProva

lunedì 31 ottobre 2016

Nuova applicazione

Ripasso ancora il riconoscimento vocale.
Ecco, ho ributtato giù un codice di riconoscimento vocale in quattro e quattr'otto, con l'aiuto dei miei appunti di "Gioco a programmare":
public class MainActivity extends AppCompatActivity {

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

        Intent intent=new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.ITALIAN);
        intent.putExtra(RecognizerIntent.EXTRA_PROMPT,"Parla ora");
        startActivityForResult(intent,0);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode==RESULT_OK){
            ArrayList lista=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            Toast.makeText(getApplicationContext(),lista.get(0),Toast.LENGTH_LONG).show();
        }

    }
}
Ora devo subordinare questa funzione a un pulsante...

Evoluzioni nell'applicazione

Ho subordinato la possibilità di spostare i folderLayout allo stato di settingMode:
        onImageTouchListener = new View.OnTouchListener(){

            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {


                X=(int)motionEvent.getRawX();
                Y=(int)motionEvent.getRawY();
                switch(motionEvent.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        if(settingMode) {
                            scrollView.requestDisallowInterceptTouchEvent(true);
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
                            deltaX = X - params.leftMargin;
                            deltaY = Y - params.topMargin;
                        }
                        break;

                    case MotionEvent.ACTION_MOVE:
                        if(settingMode) {
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
                            if (X - deltaX > 0 && X - deltaX + params.width < scrollView.getWidth())
                                params.leftMargin = X - deltaX;
                            if (Y - deltaY > 0 )
                                params.topMargin = Y - deltaY;

                            System.out.println("move " + params.leftMargin);
                            System.out.println("move " + params.topMargin);
                            view.setLayoutParams(params);
                        }
                        break;

                }


                return true;
            }
        }; 
E il comportamento è adeguato.

Ora (ho fatto un intermezzo per realizzare il collegamento di questa App al database Firebase) devo gestire il comportamento al tocco in caso di settingMode=false.

Devo ricostruire il tts.
Ma prima voglio provare l'introduzione delle textViews nascoste contenenti il TIPO e la CATEGORIA del folderLayout.
In precedenza avevo castato il layout a viewgroup, e vediamo se si può fare lo stesso... Ecco, sì, ci sono riuscito:
                    case MotionEvent.ACTION_DOWN:
                        if(settingMode) {
                            scrollView.requestDisallowInterceptTouchEvent(true);
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
                            deltaX = X - params.leftMargin;
                            deltaY = Y - params.topMargin;
                        }
                        else{
                            String tipo=(String)((TextView)((ViewGroup)view).getChildAt(2)).getText();
                            String categoria=(String)((TextView)((ViewGroup)view).getChildAt(3)).getText();
                        }
                        break;
Provando con System.out.println ottengo la stampa a video di tipo e categoria.
Ora devo sistemare il tts.

Ho fatto tanta strada senza documentarla.
tts risistemato, sistemate le coordinate delle immagini, sistemato il cambio di schermata...

giovedì 27 ottobre 2016

Considerazioni.

L'immagine sarebbe salvata come stringa.
Ecco JImage:
public class JImage {
    public String etichetta;
    public String categoria;
    public String immagine;
    public String tipo;
    public int coordX;
    public int coordY;
}


Per memorizzare il database:
STRING ETICHETTA
STRING CATEGORIA
STRING IMMAGINE
STRING TIPO


INT COORDX
INT COORDY


Riguardo il codice in cui il programma legge il database e carica le immagini nel controllo ImagView.
Per fare questo però la String deve essere ritradotta in immagine, e il codice non lo ricordo...
Vediamo...

Ecco, l'avevo già creata per la funzione di salvataggio da ImagePicker, insieme al suo reverse.
    public static String BitmapToString(Bitmap bmp, int quality){
        ByteArrayOutputStream stream=new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, quality, stream);
        byte[] b=stream.toByteArray();
        String s= Base64.encodeToString(b,Base64.DEFAULT);
        return s;
    }

 public static Bitmap StringToBitmap(String stringImage){
        byte[] b=Base64.decode(stringImage,Base64.DEFAULT);
        Bitmap bmp=BitmapFactory.decodeByteArray(b,0,b.length);
        return bmp;
    }
Per convertire Bitmap a String bisogna trasformare una bitmap in un array di bytes.
Per riconvertire String a Bitmap bisogna trasformare la String in un array di bytes.

La trasformazione passa sempre per un array di bytes.

Il campo IMMAGINE nel database è il terzo, quindi considerando che c'è una chiave primaria devo estrarre il numero 3.
Modifico la chiamata della folderLayout aggiungendovi come terzo parametro la stringa IMMAGINE presa dal database:
FolderLayout folderLayout=new FolderLayout(this,isFolder,crs.getString(3));
e ora devo andare a modificarne di conseguenza il costruttore:
public FolderLayout(Context context,boolean isFolder,String stringImage) {
...
Nel corpo di folderLayout ripristino l'immagine attraverso la funzione che ho già creato:
    private boolean isFolder;
    public FolderLayout(Context context,boolean isFolder,String stringImage) {
        super(context);
        this.context=context;
        this.isFolder=isFolder;
        setWillNotDraw(false);

        if(isFolder) {
            this.setBackground(getResources().getDrawable(R.drawable.cartella));
        }else{
            this.setBackground(getResources().getDrawable(R.drawable.document));
        }

        Bitmap b=Funzioni.StringToBitmap(stringImage);
        ImageView immagine=new ImageView(context);
        immagine.setImageBitmap(b);
        this.addView(immagine);
    }

Ecco il risultato finale, dopo una serie di aggiustamenti estetici:
Ho rimaneggiato completamente il codice di folderLayout:
public class FolderLayout extends RelativeLayout {
    Context context;




    private boolean isFolder;
    public FolderLayout(Context context,boolean isFolder,String stringImage,String etichetta) {
        super(context);
        this.context=context;
        this.isFolder=isFolder;
        setWillNotDraw(false);

        if(isFolder) {
            this.setBackground(getResources().getDrawable(R.drawable.cartella));
        }else{
            this.setBackground(getResources().getDrawable(R.drawable.document));
        }


        Bitmap b=Funzioni.StringToBitmap(stringImage);
        ImageView immagine=new ImageView(context);
        immagine.setImageBitmap(b);
        this.addView(immagine);
        RelativeLayout.LayoutParams p=(RelativeLayout.LayoutParams) immagine.getLayoutParams();
        p.topMargin=30;
        p.height=180;
        p.width=180;
        p.addRule(RelativeLayout.CENTER_HORIZONTAL);

        TextView txtEtichetta=new TextView(context);
        txtEtichetta.setText(etichetta);
        txtEtichetta.setTextSize(15);
        txtEtichetta.setSingleLine(false);
        txtEtichetta.setAllCaps(true);
        txtEtichetta.setTypeface(null, Typeface.BOLD);
        this.addView(txtEtichetta);
        System.out.println(etichetta);
        RelativeLayout.LayoutParams params =(RelativeLayout.LayoutParams)txtEtichetta.getLayoutParams();
        params.topMargin=210;
        params.leftMargin=10;
        params.addRule(RelativeLayout.CENTER_HORIZONTAL);
        params.width=250;
        params.height=150;

    }
}
ed ecco la funzione che carica il database nella finestra, allo stato attuale:
    private void leggiDaDatabase(String categoria){

        Cursor crs=helper.query(categoria);
        crs.moveToFirst();
        do{
            boolean isFolder=false;
            if(crs.getString(4).equals("categoria")) isFolder=true;
            FolderLayout folderLayout=new FolderLayout(this,false,crs.getString(3),crs.getString(1));
            mainLayout.addView(folderLayout);
            RelativeLayout.LayoutParams flParams=(RelativeLayout.LayoutParams)folderLayout.getLayoutParams();
            flParams.leftMargin=crs.getInt(5);
            flParams.topMargin=crs.getInt(6);
            flParams.width=(int)(250);
            flParams.height=(int)(300);
            folderLayout.setOnTouchListener(onImageTouchListener);


        }while(crs.moveToNext());

    }

Quest'ultima funzione è la prima che deve partire.
O, meglio, ho ricostruito la sequenza.
Non appena si avvia il programma, con l'evento OnStart, parte questo codice:
    @Override
    protected void onStart(){
        super.onStart();
        mLucchettiChangeListener.onLucchettiChange(leftLock,rightLock);
        refreshScreen();
    }
Viene innescato l'evento onLucchettiChange... (che vedrò poi);
viene chiamata la funzione refreshScreen().
    //Ricarica la schermata.
    private void refreshScreen(){

        try {
            leggiDaDatabase(currentCat);
        }catch(Exception e){}ae
        txtBarraCategoria.setText(currentCat);
    }
Questo refreshScreen innesca leggiDaDatabase avendo per parametro currentCat, che sarebbe la categoria corrente.
In più, intitola la barra categoria con il nome di questa categoria corrente.

Importante: currentCat è impostata a "iniziale":
String currentCat="iniziale";
Ed ecco leggiDaDatabase.
    private void leggiDaDatabase(String categoria){

        Cursor crs=helper.query(categoria);
        crs.moveToFirst();
        do{
            boolean isFolder=false;
            if(crs.getString(4).equals("categoria")) isFolder=true;
            FolderLayout folderLayout=new FolderLayout(this,false,crs.getString(3),crs.getString(1));
            mainLayout.addView(folderLayout);
            RelativeLayout.LayoutParams flParams=(RelativeLayout.LayoutParams)folderLayout.getLayoutParams();
            flParams.leftMargin=crs.getInt(5);
            flParams.topMargin=crs.getInt(6);
            flParams.width=(int)(250);
            flParams.height=(int)(300);
            folderLayout.setOnTouchListener(onImageTouchListener);


        }while(crs.moveToNext());

    }
Questo scorre tutto il database selezionando le sole immagini che abbiano il campo categoria impostato al valore di currentCat.
Quindi inizialmente seleziona soltanto le immagini che appartengono alla schermata iniziale.

Per ogni record crea un folderLayout contenente un'immagine e un'etichetta.
E questo è il codice che ho rimaneggiato finora.

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!!!

domenica 16 ottobre 2016

Presentazione dei risultati

Ma mi conviene davvero creare una ListView, per quattro caselle di testo?
Certo che no!
Sebbene abbia bisogno di ripassare le ListViews, meglio usare una soluzione più pratica.

Interrompo la pratica cervellotica del ripasso delle ListViews e vado a fare ciò che è più breve...

Ho disposto 4 TextView in colonna, soluzione decorosissima che mi consente anche di colorarle con maggiore facilità, anzi quasi quasi le coloro subito.
Fatto.


Ora veniamo alla parte più difficile.
Devo creare una query con dei limiti alla ricerca.
Questi limiti dovranno essere espressi come Long.
O convertiti in Long entro la funzione stessa.
Facciamo che vengono forniti come stringhe...

Funziona con questa funzione di Helper:
    public Cursor query(String minimum){
        SQLiteDatabase db=this.getWritableDatabase();
        SimpleDateFormat sdf=new SimpleDateFormat("dddd/MM/yyyy");
        Date d;
        Cursor crs;
        Long numero=0l;
        try{
            d=sdf.parse(minimum);
            numero=d.getTime();
        }catch(Exception e){}
        crs=db.rawQuery("select* from tabella where data >"+numero,null);
        crs.moveToFirst();
        return crs;
    }

Ora faccio una simulazione più aderente alla realtà, modifico il codice artificiale che mi simula l'uso dell'applicazione.
In Seconda:
                Record record;
                Date dat;
                Long n=0l;
                String[] str={"10/10/2016","10/10/2016","9/10/2016","8/10/2016","8/10/2016", "7/10/2016"};
                String[] tipi={"NUNI", "NUNI", "UNI", "UI", "UI", "NUI"};
                for(int i=0;i<4;i++) {
                    SimpleDateFormat sdf = new SimpleDateFormat("dddd/MM/yyyy");
                    try {
                        dat = sdf.parse(str[i]);
                        n = dat.getTime();
                    } catch (Exception e) {
                    }
                    record = new Record(tipi[i], n);
                    helper.save(record);
                }



Ecco il risultato finale:
public class DataReader extends AppCompatActivity {

    int NUNI,UNI,UI,NUI;
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_data_reader);
        helper=new Helper(this);

        String minimum="8/10/2016";
        String maximum="10/10/2016";
        Cursor crs=helper.query("NUNI",minimum,maximum);
        crs.moveToFirst();
        NUNI=crs.getCount();
        System.out.println("NUNI "+NUNI);

        crs=helper.query("UNI",minimum,maximum);
        crs.moveToFirst();
        UNI=crs.getCount();
        System.out.println("UNI "+UNI);

        crs=helper.query("UI",minimum,maximum);
        crs.moveToFirst();
        UI=crs.getCount();
        System.out.println("UI "+UI);

        crs=helper.query("NUI",minimum,maximum);
        crs.moveToFirst();
        NUI=crs.getCount();
        System.out.println("NUI "+NUI);

    }


}
con il quale vengono contate le varie opzioni.

Database nell'applicazione

Ecco, ho introdotto il database nell'applicazione.

Questa è la definizione della classe, ottenuta mediante l'estensione della classe SQLiteOpenHelper:
public class Helper extends SQLiteOpenHelper{
    public Helper(Context context) {
        super(context, "mioDatabase.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table tabella(tipo text, data integer)");
    }

    public void save(Record record){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("tipo",record.tipo);
        values.put("data",record.data);
        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 sqLiteDatabase, int i, int i1) {

    }
}
Ed ecco come la uso nell'activity Seconda:
        bttInvio.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {

                Record record=new Record();
                record.tipo=tipo;
                Date dateOggi=new Date();
                Long lngDateOggi=dateOggi.getTime();
                record.data=lngDateOggi;
                helper.save(record);
con conversione del tipo Date in numero Long.
Ora, però, per fare le prove mi servono records con date differenti, e questo codice immette solo la data in cui viene fatta l'immissione, ossia la data di oggi.
Per questo, commentizzerò il codice e creerò un codice che immetterà date differenti scelte ad arte.

Ho anche aggiunto un costruttore a Record, in modo da costruire meglio le singole istanze.

Funziona, sì. Ho immesso i dati nel database.
                Record record;
                Date dat;
                Long n=0l;
                String[] str={"5/9/1961","13/6/1962","10/1/2003","10/2/1935"};
                for(int i=0;i<4;i++) {
                    SimpleDateFormat sdf = new SimpleDateFormat("dddd/MM/yyyy");
                    try {
                        dat = sdf.parse(str[i]);
                        n = dat.getTime();
                    } catch (Exception e) {
                    }
                    record = new Record("fuffa", n);
                    helper.save(record);
                }
Questo è il codice che mi sono costruito per mettere dati con date molto diverse. Andrà eliminato.
Ora devo passare alla visualizzazione dei dati, la parte più nuova...

Esercizi per l'inserimento di date come Long nel database

Conversione di data, da formato String a formato Date a Long (tempo quello dell'epoch...).
Facciamo il mio compleanno, vah...

Intanto mi salvo il codice con cui stavo giocherellando ieri sera perché mi potrebbe servire, non essendo ancora esperto di questa roba...
public class MainActivity extends AppCompatActivity {

    CalendarView calendarView;

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

        calendarView=(CalendarView) findViewById(R.id.calendarView);

        Calendar calendar= Calendar.getInstance();
        SimpleDateFormat sdf=new SimpleDateFormat("dddd/MM/yyyy");
        String strDate="10/2/2016";
        try {
            Date myDate = sdf.parse(strDate);
            calendar.setTime(myDate);
        }catch(Exception e){}
        System.out.println("VALORE "+calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.DAY_OF_MONTH,calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        calendarView.setMaxDate(calendar.getTimeInMillis());




    }
}



Bene.
Vado. Da String a Date e quindi a Calendar.
public class MainActivity extends AppCompatActivity {

    CalendarView calendarView;

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

        Date myDate;
        String strDate="5/9/1961";
        Calendar calendar= Calendar.getInstance();
        SimpleDateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");
        try{
            myDate=sdf.parse(strDate);
            calendar.setTime(myDate);
            System.out.println(calendar.getTimeInMillis());
        }catch(Exception e){}
    }
}
Usando il tempo in millisecondi dalla Linux Epoch:
public class MainActivity extends AppCompatActivity {

    CalendarView calendarView;

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

        Date myDate;
        String strDate="5/9/1961";
        Calendar calendar= Calendar.getInstance();
        SimpleDateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");
        try{
            myDate=sdf.parse(strDate);
            calendar.setTime(myDate);
            Long numero=calendar.getTimeInMillis();

            calendar.setTimeInMillis(numero);
            Date secondDate=calendar.getTime();
            String str=sdf.format(secondDate);
            System.out.println("VALORE "+str);
        }catch(Exception e){}
    }
}
che funziona:
10-16 04:09:33.976 2276-2276/? I/System.out: VALORE 05/09/1961


Bene. Ora metto in un database diverse date.
Creo il database...

Ho creato la classe del database:
    public class Helper extends SQLiteOpenHelper {

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("create tabella(data integer)");
        }

        public void save(long dat){
            SQLiteDatabase db=this.getWritableDatabase();
            ContentValues values=new ContentValues();
            values.put("data",dat);
            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 sqLiteDatabase, int i, int i1) {

        }
    }
Istanzio un oggetto database come al solito...
Helper helper;

.....

helper=new Helper(this);
Ora voglio inserire quattro date sotto forma di numeri Long.
Parto dalle stringhe.
Fatto.
Ecco il codice:
public class MainActivity extends AppCompatActivity {

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

        helper=new Helper(this);
        Date myDate;
        Long numero=0l;
        String[] matrice={"5/9/1961","13/6/1962","10/1/2003","10/2/1935"};
        for(String elemento : matrice){
            System.out.println("Stringa "+elemento);
            SimpleDateFormat sdf=new SimpleDateFormat("dddd/MM/yyyy");
            try {
                myDate = sdf.parse(elemento);
                numero=myDate.getTime();
                System.out.println("NUMERO "+numero);
                helper.save(numero);

            }catch(Exception e){}


        }

    }

    class Helper extends SQLiteOpenHelper {

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("create table tabella(data integer)");
        }

        public void save(long dat){
            SQLiteDatabase db=this.getWritableDatabase();
            ContentValues values=new ContentValues();
            values.put("data",dat);
            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 sqLiteDatabase, int i, int i1) {

        }
    }
}

sabato 15 ottobre 2016

Introduzione alle date

Conversione string a date e viceversa.
Provo da String a Date.
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                String stringa="5/9/1961";
                Date dat;

                SimpleDateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");
                try {
                    dat = sdf.parse(stringa);
                    System.out.println(dat);
                }catch(Exception e){}

            }
        });
10-15 11:05:55.197 3169-3169/? I/System.out: Tue Sep 05 00:00:00 CET 1961

E da data a stringa?
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                String str;
                Date dat=new Date();

                SimpleDateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");
                str=sdf.format(dat);
                System.out.println(str);



            }
        });
10-15 12:05:05.077 5762-5762/? I/System.out: 15/10/2016

Bene.
Leggo che al posto di Date, per ottenere una data, si dovrebbe usare Calendar.
Cerchiamo di capire qualcosa sul suo uso.

Mi conviene fare e basta.
Dunque... io voglio cancellare un record del database che ha una data superiore a quella del 1 ottobre.
Prendo il record e ne tiro fuori la data, come stringa.
Come faccio a sapere se questa data è posteriore al 1 ottobre?
Innanzitutto creo un'istanza di Calendar.
Calendar calendar= Calendar.getInstance();
Ecco, adesso vedo se è possibile ottenere un tipo Date da Calendar:
                Calendar calendar= Calendar.getInstance();
                Date domani=calendar.getTime();
10-15 12:57:31.565 11035-11035/? I/System.out: Sat Oct 15 12:57:31 CEST 2016
Sì, è la data di oggi.

venerdì 14 ottobre 2016

Codice per la scelta e l'esecuzione di una suoneria personalizzata per la notifica dell'activity col bersaglio

Devo aggiungere all'activity Seconda un segnale acustico.
Non ricordo bene, ricordo che l'elemento chiave è RingtoneManager.
Vediamo...

Devo scrivere il codice per la scelta di una suoneria. Dove lo metto? Creo un'activity a parte, per il momento.

Ecco, ho creato la nuova activity SetSound, e ho impostato il codice in questo modo:
public class SetSounds extends AppCompatActivity {

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

        bttPickSound=(Button) findViewById(R.id.button5);
        bttPickSound.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                Intent intent=new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
                startActivityForResult(intent,0);
            }
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        Uri uri=data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),uri);
        ringtone.play();
    }
}
Funziona: mi consente di scegliere la suoneria.

Ora devo vedere come impostare la suoneria come default...

Seguo questo link per impostare una suoneria come default.

Ecco, intanto seguendo l'autotutorial ho scritto questo, che dovrebbe impostare tutte le suonerie di tipo NOTIFICATION a quella scelta da me ora.
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        Uri uri=data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),uri);
        RingtoneManager.setActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_NOTIFICATION,uri);
    }
Ora però io vorrei l'impostazione della suoneria alla sola applicazione, e non per tutte le notifiche.
Facciamo la prova se così si impostano tutte le suonerie di tipo notifiche.
Al momento io ho come notifica generale impostata la suoneria Skyline (assumendo sia in DO, è quella col suono "DO MI DO+ SI SOL").
Provo a cambiarla facendo girare l'applicazione...

E ci vuole permission.WRITE_SETTINGS, che provvedo a scrivere in Manifest, quindi riprovo...

Sì, perfetto!

Ora devo cambiarla in modo personalizzato.
Seguo questo autotutorial

La tecnica è quella di ripescare dalle SharedPreferences la suoneria, con un default stringa vuota.
Se non è vuota, allora Uri.parse la stringa risultante.
Per l'impostazione, porre in SharedPreferences Uri.toString.

Proviamo...

Il codice per l'impostazione, sull'activity SetSounds:
public class SetSounds extends AppCompatActivity {

    SharedPreferences SP;
    Button bttPickSound;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_set_sounds);

        SP=getApplicationContext().getSharedPreferences("suonerie", Context.MODE_PRIVATE);
        bttPickSound=(Button) findViewById(R.id.button5);
        bttPickSound.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                Intent intent=new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
                startActivityForResult(intent,0);
            }
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        Uri uri=data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),uri);
        SharedPreferences.Editor editor=SP.edit();
        editor.putString("suoneria",uri.toString());
        editor.commit();

    }
}


Il codice per la lettura, sull'activity Seconda:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_seconda);
        SP = this.getSharedPreferences("intervalli",Context.MODE_PRIVATE);
        SP2=this.getSharedPreferences("suonerie",Context.MODE_PRIVATE);


        vibrator=(Vibrator)this.getSystemService(Context.VIBRATOR_SERVICE);

        //codice per il rilevamento e l'esecuzione della suoneria personalizzata
        //(a ogni comparsa dell'activity col bersaglio)

        String strSuoneria=SP2.getString("suoneria","");
        if(TextUtils.isEmpty(strSuoneria)){
            ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_NOTIFICATION);
        }else{
            ringtoneUri=Uri.parse(strSuoneria);
        }
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
        //fine codice per rilevamento ed esecuzione della suoneria personalizzata.

Aggiunta pulsanti all'activity Seconda

Sull'activity Seconda devo rendere i due pulsanti invisibili di base e visualizzabili solo dietro pressione dei pulsanti circolari, facendoli sparire solo dietro pressione del tasto "annulla".
Procedo...

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button"
        android:visibility="invisible"
        android:layout_below="@+id/quattro"
        android:layout_marginTop="50dp"
        android:layout_alignEnd="@+id/quattro" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button4"
        android:visibility="invisible"
        android:layout_alignBottom="@+id/button"
        android:layout_toStartOf="@+id/button" 
        android:layout_marginRight="100dp"/> 
Ora si rendono visibili ambedue da codice:
faccio un esempio per tutti, il touch sul cerchio "uno":
    public void touchUno(){
        GradientDrawable gd = (GradientDrawable) uno.getBackground();
        gd.setColor(Color.BLUE);
        try {
            if (mp.isPlaying()) {
                mp.stop();
                mp.release();
                mp = MediaPlayer.create(context, R.raw.button);
            }
            mp.start();
        }catch(Exception e){e.printStackTrace();}
        bttAnnulla.setVisibility(View.VISIBLE);
        bttInvio.setVisibility(View.VISIBLE);
        bttInvio.setBackgroundColor(0xffffffff);
    }
definendo successivamente il listener per bttAnnulla...
Vediamo intanto se funziona...

Funziona.
Ora setto il listener per bttAnnulla e buonanotte!
        bttAnnulla.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                bttInvio.setVisibility(View.INVISIBLE);
                bttAnnulla.setVisibility(View.INVISIBLE);
            }
        });
E lo sperimentiamo...

Funziona.

giovedì 13 ottobre 2016

Forme particolari per i buttons.

Ora devo affrontare lo studio di come si possa dare a un button una forma complessa.

Ho creato due immagini in drawable.
Ora creo un file selector.
In esso vanno degli item.
Proviamo...

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <bitmap android:src="@drawable/stellarossa" />
    </item>

    <item>
        <bitmap android:src="@drawable/stella" />
    </item>
</selector> 
E vediamo se e come funziona...
Basterebbe solo impostare il background.
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button2"
        android:background="@drawable/tasto"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" /> 
Funziona! Ed è possibile, inoltre, fare dei pulsanti animati!

Custom Listener e Alarm On

Ora dovrei provare, come ho già fatto in passato, a creare una funzione booleana che attesti se il meccanismo Alarm è attivo o no.
Il fatto che un Alarm sia attivo dipende, se ricordo bene, dal PendingIntent.
Si chiama il PendingIntent con una flag impostata a NO_CREATE per vedere se esiste. Cerchiamo di riscrivere la cosa...

Mi sono assicurato che gli intent lavorino bene anche con questa formula:
                intent=new Intent(getApplicationContext(),Seconda.class);
                pendingIntent=PendingIntent.getActivity(getApplicationContext(),0,intent,0);
                alarmManager=(AlarmManager)getApplicationContext().getSystemService(ALARM_SERVICE);
                alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+intervalloCasuale*1000,pendingIntent);
e pare che non ci siano problemi: l'activity Seconda si presenta regolarmente all'elapse degli intervalli casuali.
Ora devo scrivere la funzione AlarmOn che individua l'Alarm congegnato con quell'intent e quel pendingIntent, in qualunque activity sia stato implementato, a quanto pare.
Eccola:
    public boolean AlarmOn(){
        intent=new Intent(getApplicationContext(),Seconda.class);
        boolean risultato=PendingIntent.getActivity(getApplicationContext(),0,intent,PendingIntent.FLAG_NO_CREATE)!=null;
        return risultato;
    }
Ora devo fare in modo che quando si apre MainActivity vi appaia il valore di AlarmOn.
Proviamo...

Ci metto una TextView.

Il risultato era sempre true, perché ho sbagliato la sintassi della funzione AlarmOn.
Quella corretta è:
    public boolean AlarmOn(){
        intent=new Intent(getApplicationContext(),Seconda.class);
        boolean risultato=(PendingIntent.getActivity(getApplicationContext(),0,intent,PendingIntent.FLAG_NO_CREATE)!=null);
        return risultato;
    }
Con questa, inizia con false, quindi una volta che si è attivato l'alarm resta sempre true.
Bisogna definire la funzione che disattiva l'alarm e vedere come attivarla.
Posso farla dipendere da un altro pulsante su MainActivity.
Inserisco un altro button.
Ecco il listener: ci devo aggiungere il codice che, annullando il pendingIntent, annulla l'Alarm.
        buttonStop.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                
            }
        });
Ricopio pari pari il codice che stabilisce Intent, PendingIntent e AlarmManager:
        buttonStop.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                intent=new Intent(getApplicationContext(),Seconda.class);
                pendingIntent=PendingIntent.getActivity(getApplicationContext(),0,intent,0);
                alarmManager=(AlarmManager)getApplicationContext().getSystemService(ALARM_SERVICE);

            }
        });
E poi ci aggiungo il codice che cancella il PendingIntent:
        buttonStop.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                intent=new Intent(getApplicationContext(),Seconda.class);
                pendingIntent=PendingIntent.getActivity(getApplicationContext(),0,intent,0);
                alarmManager=(AlarmManager)getApplicationContext().getSystemService(ALARM_SERVICE);
                alarmManager.cancel(pendingIntent);
                if(pendingIntent!=null)pendingIntent=null;
            }
        });
E vediamo se il ciclo si interrompe.
Devo settarlo a pochi secondi altrimenti lo studio è una palla tremenda!

Sembra che sia tutto OK.
Posso creare un listener per lo status dell'Alarm.
Ci provo.
Fatto e funziona tutto!

Estrazione di numero casuale fra gli intervalli di tempo minimo e massimo.

Bene.
A questo punto devo estrarre un numero casuale compreso fra due numeri interi.
Proviamo...

Ripassati i numeri casuali, vado a estrarre, appena si apre la schermata Seconda, il valore minimo e il valore massimo dagli intervalli selezionati.
Ecco il codice:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_seconda);

        SP = this.getSharedPreferences("intervalli",Context.MODE_PRIVATE);
        int intervalloMinimo=SP.getInt("intervalloMin",10);
        int intervalloMassimo=SP.getInt("intervalloMax",10);
Corrisponde ai valori salvati sulle seekBars.
Ora voglio estrarre un numero casuale fra questi due.
Ecco il mio codice, che mi consente di controllare il valore ogni volta che si apre l'activity Seconda:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_seconda);

        SP = this.getSharedPreferences("intervalli",Context.MODE_PRIVATE);
        int intervalloMinimo=SP.getInt("intervalloMin",10);
        int intervalloMassimo=SP.getInt("intervalloMax",10);
        int intervalloCasuale;
        if(intervalloMassimo==intervalloMinimo) {
            intervalloCasuale = intervalloMassimo;
        }
        else {
            Random r=new Random();
            intervalloCasuale=r.nextInt(intervalloMassimo-intervalloMinimo)+intervalloMinimo;
        }

        System.out.println("ESTREMI: "+intervalloMinimo+"; "+intervalloMassimo+", INTERVALLO CASUALE "+intervalloCasuale);
Derivo dalla memoria un intervallo minimo e uno massimo. Calcolo un numero casuale compreso fra i due.
Mi metto a video l'intervallo minimo e l'intervallo massimo, facendo attenzione che i valori calcolati non superino il primo in basso e il secondo in alto, e pare che stiamo facendo un lavoro egregio.
Ecco i risultati:
10-13 17:27:34.252 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 34
10-13 17:27:56.052 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 61
10-13 17:28:05.202 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 57
10-13 17:28:14.792 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 87
10-13 17:29:56.402 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 100
10-13 17:30:04.862 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 69
10-13 17:30:27.062 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 42
10-13 17:30:34.032 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 102
10-13 17:30:40.822 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 41
10-13 17:30:47.542 19814-19814/? I/System.out: ESTREMI: 30; 110, INTERVALLO CASUALE 39
Provo a rimaneggiare le impostazioni di tempo... le porto a minimo 1 ora e massimo 2 ore.
Vediamo...
10-13 17:35:48.952 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 80
10-13 17:35:57.222 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 108
10-13 17:36:04.162 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 79
10-13 17:36:11.412 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 110
10-13 17:36:18.212 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 97
10-13 17:36:24.592 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 96
10-13 17:36:30.932 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 90
10-13 17:36:37.242 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 97
10-13 17:36:43.542 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 100
10-13 17:36:49.902 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 86
10-13 17:36:56.212 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 106
10-13 17:37:02.702 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 62
10-13 17:37:09.352 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 67
10-13 17:37:16.112 21034-21034/? I/System.out: ESTREMI: 60; 120, INTERVALLO CASUALE 115
Sì, credo sia proprio affidabile!
Ora devo inserire il calcolo casuale negli intervalli fra le riaperture dell'activity Seconda...

Aggiustamento seekBars: memorizzazione dei settaggi di tempo in SharedPreferences

Il passo successivo è memorizzare i valori.
Ho dimenticato completamente come si utilizzano le SharedPreferences.
Innanzitutto si dichiarano sempre. Facciamolo, all'inizio del modulo.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_set_intervals);

        SP=getApplicationContext().getSharedPreferences("intervalli", Context.MODE_PRIVATE);
        vibrator=(Vibrator)this.getSystemService(VIBRATOR_SERVICE);
        textViewMin=(TextView) findViewById(R.id.textView1);
        textViewMax=(TextView) findViewById(R.id.textView2);
        seekBar1=(SeekBar)findViewById(R.id.seekBar1);
        seekBar2=(SeekBar)findViewById(R.id.seekBar2);
Se io salvo, come per fini mnemonici mi sembra meglio, il valore come intervallo, devo settare poi le seekBars a valori diminuiti di 10 diviso 10, e devo poi salvare in SharedPreferences i valori delle seekBars aumentati di 10 * 10.
Proviamo...

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

        SP=getApplicationContext().getSharedPreferences("intervalli", Context.MODE_PRIVATE);
        vibrator=(Vibrator)this.getSystemService(VIBRATOR_SERVICE);
        textViewMin=(TextView) findViewById(R.id.textView1);
        textViewMax=(TextView) findViewById(R.id.textView2);
        seekBar1=(SeekBar)findViewById(R.id.seekBar1);
        seekBar2=(SeekBar)findViewById(R.id.seekBar2);

        seekBar1.setMax(17);
        seekBar2.setMax(17);

        //Qui metto l'inizializzazione delle seekBars.
        int intervalloMinimo=SP.getInt("intervalloMin",10);
        seekBar1.setProgress((intervalloMinimo-10)/10);
E vediamo...

Sì, sembra funzionare. Devo settare anche le textView. Per fare questo meglio calcolare prima ore e minuti, come ho fatto nel codice dello scrolling delle seekBars.
        //Qui metto l'inizializzazione delle seekBars.
        int intervalloMinimo=SP.getInt("intervalloMin",30);
        System.out.println(intervalloMinimo);
        int ore=intervalloMinimo/60;
        int minuti=intervalloMinimo%60;
        seekBar1.setProgress((intervalloMinimo-10)/10);
        textViewMin.setText(ore+" ore e "+minuti+" minuti");
E adesso sembra funzionare proprio bene...
Ora devo provvedere a salvare in SharedPreferences i valori ottenuti dalle seekBars.
Per far questo devo ritornare al codice dello scorrimento di esse...

Ho recuperato e ripassato il codice.
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                vibrator.vibrate(25);
                int ore=(i*10+10)/60;
                int minuti=(i*10+10)%60;
                textViewMin.setText(ore+" ore e "+minuti+" minuti");

                if(seekBar2.getProgress()<i) seekBar2.setProgress(i);

                SharedPreferences.Editor editor=SP.edit();
                editor.putInt("intervalloMin",i*10+10);
                editor.commit();
            }
Vediamo se, salvando un valore diverso da quello di default (che ripristino a 10), questo viene memorizzato.

Sì, funziona!
Adesso sistemo anche l'altra seekBar e il lavoro risulta compiuto!
public class SetIntervals extends AppCompatActivity {

    SharedPreferences SP;
    Vibrator vibrator;
    SeekBar seekBar1, seekBar2;
    TextView textViewMin,textViewMax;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_set_intervals);

        SP=getApplicationContext().getSharedPreferences("intervalli", Context.MODE_PRIVATE);
        vibrator=(Vibrator)this.getSystemService(VIBRATOR_SERVICE);
        textViewMin=(TextView) findViewById(R.id.textView1);
        textViewMax=(TextView) findViewById(R.id.textView2);
        seekBar1=(SeekBar)findViewById(R.id.seekBar1);
        seekBar2=(SeekBar)findViewById(R.id.seekBar2);

        seekBar1.setMax(17);
        seekBar2.setMax(17);

        //Qui metto l'inizializzazione delle seekBars.
        int intervalloMinimo=SP.getInt("intervalloMin",10);
        int ore=intervalloMinimo/60;
        int minuti=intervalloMinimo%60;
        seekBar1.setProgress((intervalloMinimo-10)/10);
        textViewMin.setText(ore+" ore e "+minuti+" minuti");

        int intervalloMassimo=SP.getInt("intervalloMax",10);
        int ore2=intervalloMassimo/60;
        int minuti2=intervalloMassimo%60;
        seekBar2.setProgress((intervalloMassimo-10)/10);
        textViewMax.setText(ore2+ " ore e "+minuti2+" minuti");


        seekBar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){

            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                vibrator.vibrate(25);
                int ore=(i*10+10)/60;
                int minuti=(i*10+10)%60;
                textViewMin.setText(ore+" ore e "+minuti+" minuti");

                if(seekBar2.getProgress()<i) seekBar2.setProgress(i);

                SharedPreferences.Editor editor=SP.edit();
                editor.putInt("intervalloMin",i*10+10);
                editor.commit();
            }


            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

        seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){

            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                vibrator.vibrate(25);
                int ore=(i*10+10)/60;
                int minuti=(i*10+10)%60;
                textViewMax.setText(ore+" ore e "+minuti+" minuti");

                if(seekBar1.getProgress()>i) seekBar1.setProgress(i);

                SharedPreferences.Editor editor=SP.edit();
                editor.putInt("intervalloMax",i*10+10);
                editor.commit();

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }
}
...e pare funzionare alla grande!