Sviluppo di applicazioni Android con AndroidAnnotations

AndroidAnnotations è un framework open source che permette di utilizzare varie annotation nello sviluppo di applicazioni Android. Qualcuno che già sviluppa su Android si sarà chiesto, come mai tutti i framework Java moderni sono basati sulle annotation mentre Android non ne utilizza? Perché è stato fatto un passo indietro? La risposta è che Android è un sistema operativo per dispositivi mobile (ultimamente con processori con 4 core ma pur sempre device mobile!) e per questo deve essere leggero. La gestione delle annotation implica una lettura delle annotation all’avvio dell’applicazione. Questa lettura è abbastanza lenta (è basata sulla reflection), non è un problema per le applicazioni web che sono riavviate una volta ogni tanto ma è un problema per le app Android che devono essere molto veloci a partire.

Code generation

AndroidAnnotations per evitare problemi di prestazioni delle app si basa sull’Annotation Processing, una feature introdotta nella versione 6 della Java Platform che permette di analizzare le annotation di una classe ed eseguire del codice ad ogni compilazione. Nel caso di AndroidAnnotations ad ogni salvataggio vengono generate in automatico delle classi, in pratica una per ogni classe che contengono una annotation. Le classi generate estendono le classi scritte dallo sviluppatore e hanno lo stesso nome con un underscore in fondo. E’ in queste classi che AndroidAnnotations gestisce le varie annotations, essendo generate ad ogni salvataggio non ci sono ripercussioni sulle performance dell’applicazione (in pratica non viene usata la reflection per leggere le annotation).

Integrazione di AndroidAnnotations in un progetto Android

Per integrare AndroidAnnotations è necessario scaricare due jar (uno da usare compile time e uno con le classi da usare runtime) e includerli nel classpath del progetto. Per poter generare il codice ad ogni salvataggio è necessario configurare il progetto, questa configurazione dipende dall’ide utilizzato. Per esempio se usate Eclipse potete seguire la guida disponibile nel sito ufficiale.

Un esempio concreto: la lista delle app Android installate

Per vedere le feature di AndroidAnnotations usiamo un esempio concreto, una applicazione Android che mostra le applicazioni installate sul device in una lista. L’activity principale sviluppata senza utilizzare AndroidAnnotations è questa:

public class MainActivity extends Activity {

        private static final String APPS = "apps";
        private ArrayList apps;
        private ListView list;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);

                setContentView(R.layout.activity_main);
                WelcomeMessageManager.showWelcomeMessage(this);
                initListData(savedInstanceState);
        }

        private void initListData(Bundle state) {
                list = (ListView) findViewById(R.id.list);
                if (state != null) {
                        apps = state.getParcelableArrayList(APPS);
                }
                if (apps != null) {
                        updateListData();
                } else {
                        loadDataInBackground();
                }
        }

        private void loadDataInBackground() {
                new AsyncTask() {
                        @Override
                        protected Void doInBackground(Void... params) {
                                apps = AppsLoader.loadData(MainActivity.this);
                                return null;
                        }

                        @Override
                        protected void onPostExecute(Void result) {
                                updateListData();
                        }
                }.execute();
        }

        private void updateListData() {
                list.setAdapter(new AppAdapter(this, apps));
        }

        @Override
        protected void onSaveInstanceState(Bundle outState) {
                super.onSaveInstanceState(outState);
                outState.putParcelableArrayList(APPS, apps);
        }

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                getMenuInflater().inflate(R.menu.main_menu, menu);
                return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
                switch (item.getItemId()) {
                case R.id.menu_about:
                        SimpleTextActivity.showText(this,
                                R.string.app_name);
                        return true;
                }
                return super.onOptionsItemSelected(item);
        }
}

Questa activity richiama altre classi esterne che vedremo nel corso del post. Il codice appena visto contiene varie feature standard di Android:

  • viene mostrato un messaggio di benvenuto la prima volta che l’utente lancia l’applicazione, sono usate le SharedPreferences per ricordarsi se l’applicazione è già stata usata;
  • la lista delle applicazioni installate sul device è caricata in un thread in background usando un AsyncTask, al termine del caricamento viene aggiornata la ListView contenuta nell’activity;
  • l’elenco delle applicazioni è salvato nell’instance state dell’activity in modo da evitare un nuovo caricamento nel caso di cambio di orientation del device;
  • è usato un option menu per mostrare una finestra di about.

Gestione Activity con AndroidAnnotations

Iniziamo subito vedendo lo stesso esempio sviluppato usando AndroidAnnotations, il codice dell’activity è il seguente:

@EActivity(R.layout.activity_main)
@OptionsMenu(R.menu.main_menu)
public class MainActivity extends Activity {

        @InstanceState
        ArrayList apps;

        @ViewById
        ListView list;

        @Bean
        AppsLoader appsLoader;

        @Bean
        WelcomeMessageManager welcomeMessageManager;

        @AfterViews
        @Background
        void initListData() {
                if (apps == null) {
                        apps = appsLoader.loadData();
                }
                updateListData();
        }

        @UiThread
        void updateListData() {
                list.setAdapter(new AppAdapter(this, apps));
        }

        @OptionsItem(R.id.menu_about)
        void showAbout() {
                startActivity(SimpleTextActivity_.intent(this).text(
                        R.string.app_name).get());
        }
}

Si notano subito molte annotation, per esempio la classe è annotata con le annotation EActivity e OptionsMenu. Usando EActivity diciamo a AndroidAnnotations che la classe MainActivity è una activity che vogliamo far gestire al framework. Come già detto aggiungendo questa annotation sarà generata a ogni salvataggio una classe MainActivity_ che estende la nostra classe, nel manifest dovrà essere censita la classe con l’underscore in fondo in quanto è questa la vera classe che deve essere eseguita. Il parametro passato all’annotation è il layout da usare nell’activity, quello che solitamente viene impostato usando il metodo setContentView.

Gestione dell’option menu

Nell’esempio originale l’option menu è gestito nel modo standard di Android riscrivendo i metodi di callback onCreateOptionsMenu e onOptionsItemSelected:

public class MainActivity extends Activity {

        //...

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                getMenuInflater().inflate(R.menu.main_menu, menu);
                return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
                switch (item.getItemId()) {
                case R.id.menu_about:
                        SimpleTextActivity.showText(this,
                                R.string.app_name);
                        return true;
                }
                return super.onOptionsItemSelected(item);
        }
}

Il metodo onOptionsItemSelected contiene solitamente uno switch in cui si controlla l’id dell’item selezionato. In questo caso c’è un solo case in quanto abbiamo una sola voce nel menù ma non è raro avere molti più case per gestire tutte le varie voci. Usando AndroidAnnotation la stessa logica è implementata sfruttando due annotation:

@EActivity(R.layout.activity_main)
@OptionsMenu(R.menu.main_menu)
public class MainActivity extends Activity {

        //...

        @OptionsItem(R.id.menu_about)
        void showAbout() {
                startActivity(SimpleTextActivity_.intent(this).text(
                        R.string.app_name).get());
        }
}

Usando l’annotation OptionsMenu viene specificato la risorsa da usare per caricare le voci di menu, ogni item viene gestito con un metodo annotato OptionsItem a cui viene passato l’id dell’item. Il risparmio di codice e l’aumento di leggibilità è evidente, niente più switch infinito sull’id dell’item!

Abbiamo detto che AndroidAnnotations si basa su generazione di codice, andando a vedere l’activity generato in questa occasione troviamo questi due metodi:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater menuInflater = getMenuInflater();
    menuInflater.inflate(R.menu.main_menu, menu);
    return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    boolean handled = super.onOptionsItemSelected(item);
    if (handled) {
        return true;
    }
    int itemId_ = item.getItemId();
    if (itemId_ == id.menu_about) {
        showAbout();
        return true;
    }
    return false;
}

In pratica il codice generato è molto simile a quello che avevamo scritto manualmente nell’esempio di partenza! Ovviamente il codice generato sarà anche quello eseguito run time, questo ci fa capire che AndroidAnnotations non introduce rallentamenti nell’applicazione. Come detto la lettura delle varie annotazioni è eseguita compile time non introducendo ritardi run time dovuti all’uso della reflection.

InstanceState

Una activity Android deve salvare il proprio stato interno in modo che nel caso in cui sia distrutta e poi ricreata (per esempio per un cambio di orientation) non si perdano i dati intermedi. Nel nostro caso vogliamo salvare la lista delle applicazioni in modo da non doverla caricare più volte, il tutto avviene salvando i dati in un Bundle nel metodo onSaveInstanceState che poi viene passato al successivo avvio nel metodo onCreate:

public class MainActivity extends Activity {

        private static final String APPS = "apps";
        private ArrayList apps;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                //...
                initListData(savedInstanceState);
        }

        private void initListData(Bundle state) {
                list = (ListView) findViewById(R.id.list);
                if (state != null) {
                        apps = state.getParcelableArrayList(APPS);
                }
                //...
        }

        //...

        @Override
        protected void onSaveInstanceState(Bundle outState) {
                super.onSaveInstanceState(outState);
                outState.putParcelableArrayList(APPS, apps);
        }
}

Come si può fare la stessa cosa usando AndroidAnnotations? Facile, basta annotare il campo che vogliamo salvare con l’annotation InstanceState:

@InstanceState
ArrayList apps;

View

Usando il metodo findViewById è possibile ottenere una view ricercandola all’interno del layout impostato nell’activity. Questo metodo è abbastanza lento (soprattutto in layout complessi) e quindi deve essere invocato meno volte possibile. Un buon modo per evitare chiamate multiple è quello di invocare questo metodo nell’onCreate dell’activity (o del fragment) e memorizzare la view in un campo della classe:

public class MainActivity extends Activity {

        private ListView list;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                list = (ListView) findViewById(R.id.list);
                //...
        }
        //...
}

AndroidAnnotations mette a disposizione l’annotation ViewById per fare la stessa identica cosa:

@EActivity(R.layout.activity_main)
public class MainActivity extends Activity {

        @ViewById
        ListView list;

        //...
}

L’id della view da ricercare nel layout è uguale al nome del campo, per impostare un id diverso dal nome basta passare un parametro all’annotation. Se abbiamo bisogno di usare una view non possiamo mettere il nostro codice dentro il metodo onCreate dell’activity in quando in questo metodo ancora il campo non è stato popolato. Per ovviare a questo problema basta scrivere un metodo e annotarlo con AfterViews:

@EActivity(R.layout.activity_main)
public class MainActivity extends Activity {

        @ViewById
        ListView list;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                System.out.println(list == null);
        }

        @AfterViews
        void initList() {
                System.out.println(list != null);
        }

        //...
}

Passaggio dei parametri ad una activity

Per richiamare una activity il metodo standard di Android è quello di usare un Intent che specifica la classe dell’activity da usare e gli eventuali parametri da passare. I parametri sono in un mappa, la mappa è non tipata e quindi compile time non abbiamo la certezza che i parametri siano passati in modo corretto (ovvero che la chiave della mappa e il tipo dei parametri siano corretti). Inoltre l’autocomplete del nostro ide di fiducia non ci è di aiuto, al massimo possiamo documentare in un javadoc i parametri da passare e/o sbirciare il codice per vedere l’utilizzo. Nel nostro esempio abbiamo creato una activity che mostra un messaggio passato nell’Intent (ovviamente è solo un esempio per vedere il passaggio dei parametri, la stessa cosa poteva essere fatta in altri modi più semplici). Attraverso il metodo statico showText l’activity viene creata e invocata con il corrispondente parametro, nel metodo onCreate il parametro viene letto dall’Intent:

public class SimpleTextActivity extends Activity {
        private static final String TEXT = "TEXT";

        public static void showText(Context context, int text) {
                Intent intent = new Intent(context,
                        SimpleTextActivity.class);
                intent.putExtra(SimpleTextActivity.TEXT, text);
                context.startActivity(intent);
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                requestWindowFeature(Window.FEATURE_NO_TITLE);
                setContentView(R.layout.simple_text_layout);
                int text = getIntent().getIntExtra(TEXT, 0);
                TextView textView = (TextView) findViewById(R.id.text);
                textView.setText(text);
        }
}

AndroidAnnotations mette a disposizione l’annotation Extra, annotando su un campo della classe con questa annotation il campo viene considerato un parametro dell’activity:

@EActivity(R.layout.simple_text_layout)
@NoTitle
public class SimpleTextActivity extends Activity {
        @Extra
        int text;

        @ViewById(R.id.text)
        TextView textView;

        @AfterViews
        void initText() {
                textView.setText(text);
        }
}

In questo esempio è usato anche l’annotation NoTitle per non mostrare il title dell’activity. Per richiamare l’activity dovremmo usare la classe con l’underscore in fondo, il tutto può essere semplificato usando un builder generato in automatico nella classe. Se non conoscete il concetto di builder vi suggerisco di leggere il nostro che approfondisce l’argomento. Usando questo builder possiamo anche passare i parametri in modo sicuro in quanto viene generato un metodo per ogni parametro:

startActivity(SimpleTextActivity_.intent(this).text(
        R.string.app_name).get());

Una cosa simile può essere usata anche per passare parametri a un Fragment, i campi del fragment devono essere annotati con FragmentArg.

Bean e dependency injection

Sviluppando una applicazione Android un po’ più complicata di Hello World ci si imbatte in breve tempo in problemi di organizzazione del codice. Senza usare accortezze (e un po’ di refactoring!) tutto il codice viene messo dentro una o più Activity creando delle classi difficili da leggere e manutenere. Con l’avvento dei fragment le cose sono migliorate molto ma si può fare di meglio spostando parte del codice in altre classi, vediamo come AndroidAnnotations ci può aiutare per queste cose.

I concetti di Inversion of Control e Dependency Injection sono molto usati soprattutto in ambito di applicazione Web J2EE. Il framework Spring ne fa largo uso ma anche usando JSF, EJB3 o CDI possiamo far ricorso a questi pattern. Usando la dependency injection le classi Java sono solitamente organizzate meglio e si migliora la coesione e il disaccoppiamento delle varie classi.

Usando AndroidAnnotations è possibile creare un bean usando l’annotation EBean:

@EBean
public class AppsLoader {

        @RootContext
        Context context;

        public ArrayList loadData() {
                PackageManager pm = context.getPackageManager();
                Intent mainIntent = new Intent(Intent.ACTION_MAIN,
                        null);
                mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
                List activities =
                        pm.queryIntentActivities(mainIntent, 0);
                ArrayList apps = new ArrayList();

                for (ResolveInfo resolveInfo : activities) {
                        apps.add(new App(
                                resolveInfo.loadLabel(pm).toString(),
                                resolveInfo.activityInfo.packageName));
                }
                return apps;
        }
}

Ovviamente all’interno di un bean è possibile usare tutte le altre annotation, per esempio è possibile anche referenziare una view dell’activity in cui è usato il bean usando l’annotation ViewById. In questo esempio vediamo l’utilizzo di RootContext per avere il riferimento al context in cui il bean è usato, questa annotation è molto utile in quanto permette di non passare il context come parametro ai metodi semplificandone la signature.

Gestione thread in background

Così come avviene in altri linguaggi di programmazione o in altri ambienti java anche in Android le operazioni complesse devono essere eseguite in un thread in background. Infatti tutti gli eventi vengono gestiti in un unico thread, se in questo thread viene eseguita una operazione lunga (per esempio una chiamata a un servizio rest) questa operazione blocca la gestione degli eventi che restano in coda fino a che il thread non si libera. Su Android se un evento non è gestito entro 5 secondi viene mostrato l’odioso popup che chiede all’utente se vuole aspettare o chiudere forzatamente l’applicazione.

I task devono essere quindi eseguiti in un thread in background ma l’aggiornamento dei componenti grafici deve avvenire comunque nel thread principale. Anche questo è un classico di molti ambienti, per non avere problemi di concorrenza tutte le modifiche alla interfaccia grafica devono essere eseguiti nel thread in cui sono gestiti anche gli eventi. I modi per eseguire un task in background e il successivo aggiornamento dell’interfaccia grafica su Android sono molteplici:

  • un buon vecchio Thread Java funziona anche su Android, l’aggiornamento dell’interfaccia può essere fatto sfruttando il metodo runOnUiThread a cui viene passato un oggetto Runnable con la logica da eseguire sul thread principale;
  • la classe AsyncTask di Android permette di eseguire task in background e relativo aggiornamento della ui riscrivendo alcuni metodi. Permette anche di gestire risultati intermedi e la cancellazione del task. Chi l’ha usata sa che non è proprio semplice da capire e da usare, il fatto che sia una classe con tre parametri generics dice già molto della sua complessità;
  • i Loader sono stati introdotti con Android 3.0 Honeycomb ma sono utilizzabili anche nelle precedenti versioni in quanto sono disponibili anche nella Android compatibility library. Usando un Loader possiamo eseguire task in background e abbiamo anche l’ulteriore vantaggio che il cambio di orientation viene gestito in automatico dal framework Android.

Un esempio di un AsyncTask molto semplice in cui carichiamo la lista delle applicazioni installate sul device è il seguente:

private void loadDataInBackground() {
        new AsyncTask() {
                @Override
                protected Void doInBackground(Void... params) {
                        apps = AppsLoader.loadData(MainActivity.this);
                        return null;
                }

                @Override
                protected void onPostExecute(Void result) {
                        updateListData();
                }
        }.execute();
}

In questo caso non abbiamo sfruttato i parametri generics (che indicano gli eventuali parametri, i risultati intermedi e i risultati del task). Abbiamo semplicemente riscritto i metodi doInBackground in cui viene eseguito il task in background e onPostExecute in cui viene effettuato l’aggiornamento dell’interfaccia grafica nel thread principale.

Per gestire questa problematica AndroidAnnotations mette a disposizione due annotations: Background e UiThread. Queste annotation possono essere usate sui metodi di una classe: come suggerisce il nome un metodo annotato con Background viene eseguito automaticamente in un thread in background mentre un metodo annotato con UiThread viene eseguito nel thread principale. L’esempio visto prima diventa semplicemente questo:

@AfterViews
@Background
void initListData() {
        if (apps == null) {
                apps = appsLoader.loadData();
        }
        updateListData();
}

@UiThread
void updateListData() {
        list.setAdapter(new AppAdapter(this, apps));
}

Il codice è molto semplice e c’è da notare che non è necessario creare classi interne che risultano spesso poco leggibili.

Salvataggio dati con le Shared Preferences di Android

L’Android framework mette a disposizione le Shared Preferences per salvare dati primitivi in modo persistente, in pratica è possibile salvare una mappa di valori in un file xml sul device in modo molto semplice. Un esempio in cui viene salvato un boolean che indica se è il primo utilizzo dell’app è il seguente:

public class WelcomeMessageManager {

        private static final String FIRST_TIME = "FIRST_TIME";

        public static void showWelcomeMessage(Context context) {
                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
                if (prefs.getBoolean(FIRST_TIME, true)) {
                        SimpleTextActivity.showText(context,
                                R.string.welcome);
                        prefs.edit().putBoolean(FIRST_TIME, false).commit();
                }
        }

}

E’ facile notare che la mappa di valori è non tipata, in questo caso abbiamo usato una costante per evitare errori di battitura ma non abbiamo la sicurezza compile time che la chiave sia corretta e che il tipo del valore sia quello che ci aspettiamo.

Vediamo come riscrivere lo stesso codice con AndroidAnnotations, per prima cosa scriviamo una interfaccia che contiene un metodo per ogni valore che vogliamo salvare nelle Shared Preferences:

@SharedPref(Scope.APPLICATION_DEFAULT)
public interface FirstTimePref {
        @DefaultBoolean(true)
        boolean firstTime();
}

L’interfaccia deve essere annotata con SharedPref (in questo caso abbiamo specificato anche lo scope in cui saranno salvati i dati), ogni metodo può essere annotato per specificare il valore di default da usare. Una volta che abbiamo l’interfaccia definita possiamo usarla per leggere e scrivere le preferenze:

@EBean
public class WelcomeMessageManager {

        @RootContext
        Context context;

        @Pref
        FirstTimePref_ prefs;

        @AfterViews
        public void showWelcomeMessage() {
                if (prefs.firstTime().get()) {
                        context.startActivity(SimpleTextActivity_.intent(
                                context).text(R.string.welcome).get());
                        prefs.edit().firstTime().put(false).apply();
                }
        }
}

Anche in questo caso l’oggetto è un campo in una classe, stavolta l’annotation da usare è Pref. Da notare che a differenza degli altri esempi la classe da usare è quella generata da AndroidAnnotations e non l’interfaccia che abbiamo scritto in precedenza. Sull’oggetto prefs possiamo richiamare i metodi per leggere e scrivere i nostri dati, stavolta è tutto tipato ed eventuali inconsistenze sono segnalate compile time con tutti i benefici del caso.

Pregi e difetti di AndroidAnnotations

Come evidenziato in questo post i benefici nell’utilizzo di AndroidAnnotations sono molteplici. I principali sono:

  • codice più semplice da leggere
  • migliore organizzazione del codice grazie alla DI
  • le classi generate sono nel progetto, il debug non è più complicato dei progetti standard

Ma ci sono anche dei difetti in questo framework? Secondo me il principale è che ogni tanto ci sono dei casi un po’ strani tipo errori in compilazione inspiegabili o cose simili. Quasi sempre il tutto si risolve con un classico clean di Eclipse. Comunque niente di preoccupante, i pregi sono sicuramente di valore e consiglio a tutti di usare questo framework nei propri progetti Android. Sicuramente nei progetti nuovi (a proposito, se dovete creare un nuovo progetto date un’occhiata a androidkickstartr.com) ma anche nei progetti esistenti in quanto AndroidAnnotations può essere introdotto gradualmente senza troppi problemi.

Fabio Collini

Software Architect con esperienza su piattaforma J2EE e attualmente focalizzato principalmente in progetti di sviluppo di applicazioni Android. Attualmente sono in Nana Bianca dove mi occupo dello sviluppo di alcune app Android. Coautore della seconda edizione di e docente di corsi di sviluppo su piattaforma Android. -

  • Marco

    Ciao Fabio,
    ho letto l’articolo e non conoscevo l’uso delle annotations con Android, certo è che riducono notevolmente il codice da scrivere rendendo molto più pulito il codice. Sicuramente le proverò(adesso sono immerso in andengine).
    Per i problemi di compilazione inspiegabili indubbiamente è colpa anche di eclipse, ed io ultimamente sono passato a intellij(solo per android) e non ho più questi problemi.

    • Fabio Collini

      Ciao, tutti quelli che passano a intellij ne parlano bene ma per adesso non l’ho mai usato seriamente. Android Studio è basato su intellij e spero di provarlo presto, anche se è ancora alla versione 0.2 e un po’ questa cosa mi spaventa!

  • PremierITA

    Ciao Fabio,

    io ho un problema, non riesco a far funzionare insieme androidannotation-3.0 (che dovrebbe essere l’ultima versione ad oggi) ed eclipse Kepler.

    In pratica ho seguito tutti i passaggi segnati al seguente url

    http://goo.gl/ehTdSI

    per la configurazione dell’ambiente, ma non appena inserito il jar per Annotation Preprocessor, eclipse mi notifica che Java Builder è fallito perchè non trova il file EActivity. Quel file in effetti dovrebbe essere presente in androidannotation-api-3.0 che è il jar che invece ho aggiunto al build path del mio progetto.

    Tu ci sei riuscito? come hai fatto?
    Grazie,
    ciao

    • Fabio Collini

      Ciao, per quello che so io la versione 3.0 non è ancora stata rilasciata ufficialmente. Nel sito di AndroidAnnotations (anche nel link che hai incollato te) si parla della versione 2.7.1. Può darsi che sia un bug usando questo framework in combinazione con l’ultima versione di Eclipse, ti consiglio di postare il tuo problema sul gruppo ufficiale:

      Ciao, Fabio