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

venerdì 24 febbraio 2017

Firebase Test Lab

Un aspetto dello sviluppo Android che puo' mandare fuori di testa e' quello di avere segnalazioni di crash dagli utenti ma non riuscire a ripeterli. Un ottimo sistema per gestire il Crash Report e' gia' stato visto su Firebase ma la soluzione ideale e' quella di testare l'applicazione prima di rilasciarla

Su questo aspetto viene incontro Test Lab di Firebase. Con questo strumento si puo' testare su un certo numero di dispositivi fisici o virtuali l'applicazione in un contesto automatizzato.

Dipendentemente dal tipo di profilo (il mio e' quello gratuito) esistono un limite giornaliero di test di apk. Per prima cosa si carica il file


Poi si devono identificare le piattaforme di test (in alto i dispositivi fisici, in basso quelli virtuali)


Dopo circa 5 minuti sono riportati gli eventuali errori


con il dettaglio per il debug


Ho avuto modo di provarlo se un caso reale e si e' rivelato uno strumento molto utile ed affidabile

mercoledì 22 febbraio 2017

Crash Report con Firebase su Android

Sto sviluppando una applicazione, non destinata al PlayStore, da usare in un gruppo ristretto di persone. Al momento c'e' il problema e' che l'applicazione funziona perfettamente sul mio Nexus 5 con Android 6.0.1 ma crasha miseramente su un Samsung Galaxy A3 sempre con Android 6.0.1

Non potendo avere il telefono A3 a disposizione per le prove l'unica soluzione e' quella di usare una libreria di crash report. Il supporto di Firebase e' semplicemente fantastico e semplice da implementare

Esiste una procedura automatica interna ad Android Studio per aggiungere il crash reporting ad un progetto gia' esistente. Si parte dal menu' Tools e si seleziona Firebase



Si seleziona quindi crash reporting tra i vari servizi forniti. 


Si arriva quindi al menu della pagina sottostante


Cliccando sui due pulsanti (Connect to Firebase ed Add Crash....) il progetto viene configurato in modo automatico....i meglio dovrebbe. Seguendo la procedura automatica e tenendo la build del progetto viene generato l'errore Execution failed app:processDebugGoogleServices missing api_key/current..la soluzione e' quello di inserire a mano il file google-services.json a mano nella root del progetto.

Da qui in poi e' una passeggiata. Ogni volta che viene generato un errore, l'applicazione la notifichera' sulla consolle di Firebase con un ritardo massimo di un paio di minuti



lunedì 2 gennaio 2017

Integrazione Google Maps e Firebase per mappatura collaborativa

Firebase puo' essere utilizzato come base dati per salvare anche dati semplici di tipo cartografico.
Il vantaggio di questo approccio e' che ogni modifica effettuata da un client viene immediatamente visualizzato sugli altri client senza necessita' di aggiornare la pagina (utile quindi nel caso si voglia fare per esempio il tracking di un oggetto che si muove)
Un esempio live puo' essere provato a questo link


La struttura dati e' molto semplice: un alberi in cui ad ogni entrata sono presenti una longitudine ed una latitudine. Cliccando sulla mappa si aggiunge un nuovo nodo alla mappa
Esempio del sistema in uso




Attenzione : Firebase non conosce il tipo float per cui ci sono un po' di magheggi nel convertire i dati float da e per tipi stringhe
------------------------
<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      html, body { height: 100%; margin: 0; padding: 0; }
      #map { height: 100%; }
    </style>
   
    <script src="https://www.gstatic.com/firebasejs/3.6.4/firebase.js"></script>
    <script type="text/javascript">  

    var latitudine = 0.0;
    var longitudine = 0.0;
    var conta = 0;
    // Initialize Firebase
  var config = {
    apiKey: "AIzaSyAchx8oVeLUgYnGujAEfwOxxxxxxxxxxxxxxxx",
    authDomain: "notifica-23xxxxxxfirebaseapp.com",
    databaseURL: "https://notifica-23xxxxxfirebaseio.com",
    storageBucket: "notifica-2xxxxxx.appspot.com",
    messagingSenderId: "392723xxxxxxx"
  };
  firebase.initializeApp(config);
</script>
<script type="text/javascript">

/*
questi sono solo esempi di come si possano inserire dati in Firebase in Javascript
le righe sono commentate e quindi non in uso/
firebase.database().ref('punti/').set({
    lat: 43,
    lng : 11
  });*/

/*

firebase.database().ref('punti/').push({
    lat: 43.0,
    lng : 12.0
  });

firebase.database().ref('punti/').push({

    lat: 42,
    lng: 11
  });
*/

  </script>


  </head>
  <body>
    <div id="map"></div>

   

  <script>

  function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: {
        lat: 43,
        lng: 11
    },
    zoom: 6,
    styles: [{
        featureType: 'poi',
        stylers: [{
                visibility: 'off'
            }] // Turn off points of interest.
    }, {
        featureType: 'transit.station',
        stylers: [{
                visibility: 'off'
            }] // Turn off bus stations, train stations, etc.
    }],
    disableDoubleClickZoom: false
   });

  //questa e' la funzione che all'evento click sulla mappa aggiunge il corrispondente
  // punto nel database    map.addListener('click', function(e) {
    firebase.database().ref('punti/').push({
    lat: JSON.stringify(e.latLng.lat()),
    lng : JSON.stringify(e.latLng.lng())
   });
  var marker = new google.maps.Marker({
    position: {
      lat: e.latLng.lat(),
      lng: e.latLng.lng()
    },
    map: map
    });
  });
 //fine della funzione di inserimento
 // crea un evento sull'aggiornamento dei punti
//sicuramente esiste un sistema piu' furbo di fare la stessa cosa
  const dbRefPunti = firebase.database().ref().child('punti');
  dbRefPunti.on('child_added',function(snap,prevChildKey)
    {
      var la1;
      var lo1;
      snap.forEach(function (snapshot) {

           var obj = snapshot.val();

           var nome = snapshot.key;
           if (snapshot.key == "lat")
              {
                latitudine = JSON.stringify(obj);
                la1 = obj;
                console.log("latitudine : "+latitudine);
              }
           if (snapshot.key == "lng")
                {
                longitudine = JSON.stringify(obj);
                lo1 = obj;
                console.log("longitudine : "+longitudine);
              }
            conta++;
            console.log("Conta : "+conta);

            if (conta%2 == 0) 

              {
              console.log("Stampa : " + latitudine+":"+longitudine);
              console.log(isNaN(la1));


               //var latLng = new google.maps.LatLng(parseFloat(latitudine),parseFloat(longitudine));

               var latLng = new google.maps.LatLng(la1,lo1);

               var marker = new google.maps.Marker({position: latLng,map: map});



              }

           });
       }

    );

  // fine evento

  //fine initMaps

  google.maps.event.addDomListener(window, "load", initMap);

  };


  </script>
  <script async defer
    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDkeozFISVmCAB3ta1rEXvMNDDzSxxxxxxxx&callback=initMap">
    </script>
   
  </body>
</html>
------------------------

venerdì 30 dicembre 2016

Firebase Storage

A differenza del serivizio Hosting, con il quale potrebbe essere confuso per alcuni usi comuni, lo Storage di Firebase impiega un sistema nativo di permessi per cui e' possibile rendere i contenuti privati e pubblici con un filtro sulla base dell'utente.
Inoltre se in Hosting l'aggiornamento avviene con una metodologia simile ai commit di GitHub, ovvero a livello di intero progetto, in Storage si gestiscono i singoli file

Nel profilo gratuito sono inclusi 5Gb di spazio disco per lo Storage (1 Gb per Hosting)



Per recuperare i file si ha a disposizione un link del tipo

https://firebasestorage.googleapis.com/v0/b/notifica-23425.appspot.com/o/images%2Fa1.jpg?alt=media&token=4f930e8d-cxxxxxxxxxxxxxxxxxxxxxx


Esempio di accesso non autenticato
oppure e' possibile scaricare il medesimo file utilizzando le URI di Google Cloud (indirizzo del formato gs://)

i permessi di accesso vengono gestiti dalla Console di Firebase sul tab Regole (come in Realtime Databases)



Per interagire con Storage, oltre alla Console, si possono usare client Javascript, Android ed IOS (operazioni di Upload, Download e Delete)




martedì 27 dicembre 2016

Firebase Database con Android

In questo post sono indicati i passi per interfacciare Android con i database di Firebase.
Il tutto e' stato ripreso dal tutorial ufficiale



Come per il caso delle notifiche si deve inserire nelle SDK tools Google Play Services e Repository



e si deve aggiungere il file google.json nella directory /app

build.gradle (Project)
--------------------
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'        
        classpath 'com.google.gms:google-services:3.0.0'
        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
--------------------

build.gradle (app)
--------------------
apply plugin: 'com.android.application'
android {
    compileSdkVersion 25    buildToolsVersion "23.0.1"    
    defaultConfig {
    applicationId "com.luca_innocenti.dbfirebase"        
    minSdkVersion 19        
    targetSdkVersion 25        
    versionCode 1        
    versionName "1.0"        
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }
    buildTypes {
        release {
            minifyEnabled false            
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }
    }
}

dependencies {
    compile  'com.google.firebase:firebase-core:10.0.0'    
    compile  'com.google.firebase:firebase-database:10.0.0'
    compile  'com.google.firebase:firebase-auth:10.0.0'

    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'    })
    compile 'com.android.support:appcompat-v7:25.1.0'    
   compile 'com.android.support:design:25.1.0'    testCompile 'junit:junit:4.12'}

apply plugin: 'com.google.gms.google-services'
--------------------

Si parte creando una basic activity che abbia il nome corrispondente con quella selezionata nel DB




Nella activity sono implementata una lettura ed un update di un campo del DB

per effettuare l'update si devono modificare i permessi del DB. Dato che era una prova ci sono permessi di lettura e scrittura senza limitazioni (Firebase avvisa in modo vistoso che e' una scelta da non fare)



Activity
------------------------------
package com.luca_innocenti.dbfirebase;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {

    TextView mConditionTextView;
    Button mButton;
    Button mLogin;
    Button mInsert;

    DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();
    DatabaseReference mConditionRef = mRootRef.child("condition");

    private FirebaseAuth mAuth;
    private FirebaseAuth.AuthStateListener mAuthListener;
    private static final String TAG = "EmailPassword";



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

        mConditionTextView = (TextView) findViewById(R.id.textView2);
        mButton = (Button) findViewById(R.id.button2);
        mInsert = (Button) findViewById(R.id.button5);
        mLogin = (Button) findViewById(R.id.button4);

        // [START initialize_auth]        mAuth = FirebaseAuth.getInstance();
        // [END initialize_auth]
        // [START auth_state_listener]        mAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    // User is signed in                    Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
                } else {
                    // User is signed out                    Log.d(TAG, "onAuthStateChanged:signed_out");
                }
                // [START_EXCLUDE]                // [END_EXCLUDE]            }
        };
        // [END auth_state_listener]
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }


    @Override    protected void onStart()
    {
        super.onStart();
        mAuth.addAuthStateListener(mAuthListener);

//qui viene aggiunto il listener, ogni volta che viene modificato il campo di interesse
// il valore modificato viene letto e mostrato nella textview 
        mConditionRef.addValueEventListener(new ValueEventListener() {
            @Override            public void onDataChange(DataSnapshot dataSnapshot) {
                String text = dataSnapshot.getValue(String.class);
                mConditionTextView.setText(text);
            }

            @Override            public void onCancelled(DatabaseError databaseError) {

            }
        });
// nel caso che si prema un
        mButton.setOnClickListener(new View.OnClickListener(){
            @Override            public void onClick(View view) {
              mConditionRef.setValue("Update");
            }

        });

        mInsert.setOnClickListener(new View.OnClickListener(){
            @Override            public void onClick(View view) {
                mConditionRef.setValue("Update");
            }

        });

        mLogin.setOnClickListener(new View.OnClickListener(){
            @Override            public void onClick(View view) {
                signIn("lucainnoc@gmail.com","chiara");
            }

        });


    }

    @Override    public void onStop() {
        super.onStop();
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);
        }
    }

    private void signIn(String email, String password) {
        Log.d(TAG, "signIn:" + email);


        // [START sign_in_with_email]        mAuth.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInWithEmail:onComplete:" + task.isSuccessful());

                        // If sign in fails, display a message to the user. If sign in succeeds                        // the auth state listener will be notified and logic to handle the                        // signed in user can be handled in the listener.                        if (!task.isSuccessful()) {
                            Log.d(TAG, "signInWithEmail:failed", task.getException());
                            Toast.makeText(getApplicationContext(), "Fallito",Toast.LENGTH_SHORT).show();
                        }

                        // [START_EXCLUDE]                        if (!task.isSuccessful()) {
                            Log.d(TAG, "signInWithEmail:successo");
                            Toast.makeText(getApplicationContext(), "Riuscito",Toast.LENGTH_SHORT).show();

                        }
                        // [END_EXCLUDE]                    }
                });
        // [END sign_in_with_email]    }


    @Override    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will        // automatically handle clicks on the Home/Up button, so long        // as you specify a parent activity in AndroidManifest.xml.        int id = item.getItemId();

        //noinspection SimplifiableIfStatement        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}
------------------------------

E adesso la prova modificando i dati sulla consolle di Firebase ed osservando le modifiche sull'emulatore Android (caricato con una immagine con Play Services)






Autenticazione Firebase con Android

Esempio di autenticazione basata su email/password su Firebase usando un client Android (ripreso dal GitHub ufficiale)
Per prima cosa dalla Consolle di Firebase si deve abilitare l'autenticazione mail/password



e si aggiunge un utente




build.gradle (progetto)
--------------
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'        classpath 'com.google.gms:google-services:3.0.0'
        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
--------------

build.gradle (app)
--------------
apply plugin: 'com.android.application'
android {
    compileSdkVersion 25    buildToolsVersion "23.0.1"    defaultConfig {
        applicationId "com.luca_innocenti.dbfirebase"        minSdkVersion 19        targetSdkVersion 25        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }
    buildTypes {
        release {
            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }
    }
}

dependencies {
    compile  'com.google.firebase:firebase-core:10.0.0'
    compile  'com.google.firebase:firebase-auth:10.0.0'

    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'    })
    compile 'com.android.support:appcompat-v7:25.1.0'    compile 'com.android.support:design:25.1.0'    testCompile 'junit:junit:4.12'}
apply plugin: 'com.google.gms.google-services'
--------------


--------------
package com.luca_innocenti.dbfirebase;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import org.w3c.dom.Text;
import java.util.HashMap;import java.util.Map;
public class MainActivity extends AppCompatActivity {

    TextView mConditionTextView;
    Button mButton;
    Button mLogin;
    Button mInsert;

    DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();
    DatabaseReference mConditionRef = mRootRef.child("condition");
    private FirebaseAuth mAuth;
    private FirebaseAuth.AuthStateListener mAuthListener;
    private static final String TAG = "EmailPassword";


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

        mConditionTextView = (TextView) findViewById(R.id.textView2);
        mButton = (Button) findViewById(R.id.button2);
        mInsert = (Button) findViewById(R.id.button5);
        mLogin = (Button) findViewById(R.id.button4);

        // [START initialize_auth]       
       //  Controlla se all'avvio l'utente e' gia' autenticato
                         mAuth = FirebaseAuth.getInstance();
        // [END initialize_auth]
        // [START auth_state_listener]        
                mAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                    FirebaseUser user = firebaseAuth.getCurrentUser();
                            if (user != null) {
                                  // User is signed in                    Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
                                  } else {
                                 // User is signed out                    Log.d(TAG, "onAuthStateChanged:signed_out");
                                 }
                      // [START_EXCLUDE]                // [END_EXCLUDE]            }
               };
        // [END auth_state_listener]


        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }


    @Override    protected void onStart()
    {
        super.onStart();
        mAuth.addAuthStateListener(mAuthListener);


        mConditionRef.addValueEventListener(new ValueEventListener() {
            @Override            public void onDataChange(DataSnapshot dataSnapshot) {
                String text = dataSnapshot.getValue(String.class);
                mConditionTextView.setText(text);
            }

            @Override            public void onCancelled(DatabaseError databaseError) {

            }
        });
        mButton.setOnClickListener(new View.OnClickListener(){
            @Override            public void onClick(View view) {
              mConditionRef.setValue("Update");
            }

        });

        mInsert.setOnClickListener(new View.OnClickListener(){
            private DatabaseReference usersRef;

            public void onClick(View view) {
                usersRef = mRootRef.child("condition");

                Map<String,Object> nick = new HashMap<String, Object>();
                nick.put("nome","Luca");
                usersRef.setValue(nick);
                Log.d(TAG,"inserimento");

            }

        });
         

       //se si clicca il pulsante viene richiamata la procedura di autenticazione
      // per semplicita' username e password sono statici
        mLogin.setOnClickListener(new View.OnClickListener(){
            @Override            public void onClick(View view) {
                signIn("lucainnoc@gmail.com","xxxxxxxx");
            }

        });


    }

    @Override    public void onStop() {
        super.onStop();
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);
        }
    }

//Funzione di autenticazione 
    private void signIn(String email, String password) {
        Log.d(TAG, "signIn:" + email);


        // [START sign_in_with_email]        mAuth.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInWithEmail:onComplete:" + task.isSuccessful());

                        // If sign in fails, display a message to the user. If sign in succeeds                        
                       // the auth state listener will be notified and logic to handle the                      
                      // signed in user can be handled in the listener.                       
                       if (!task.isSuccessful()) {
                            Log.d(TAG, "signInWithEmail:failed", task.getException());
                            Toast.makeText(getApplicationContext(), "Fallito",Toast.LENGTH_SHORT).show();
                        }

                        // [START_EXCLUDE]                        if (!task.isSuccessful()) {
                            Log.d(TAG, "signInWithEmail:successo");
                            Toast.makeText(getApplicationContext(), "Riuscito",Toast.LENGTH_SHORT).show();

                        }
                        // [END_EXCLUDE]                    }
                });
        // [END sign_in_with_email]    }


    @Override    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will        
// automatically handle clicks on the Home/Up button, so long        // as you specify a parent activity in AndroidManifest.xml.        int id = item.getItemId();

        //noinspection SimplifiableIfStatement        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}
-------------

Debugger integrato ESP32S3

Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede  nell'ID USB della porta Jtag. Nel modulo...