{"id":11909,"date":"2016-08-31T10:42:27","date_gmt":"2016-08-31T08:42:27","guid":{"rendered":"http:\/\/www.devapp.it\/wordpress\/?p=11909"},"modified":"2016-08-31T10:42:27","modified_gmt":"2016-08-31T08:42:27","slug":"come-caricare-i-dati-in-modo-asincrono-usando-i-loader-nello-sviluppo-di-applicazioni-android","status":"publish","type":"post","link":"https:\/\/www.devapp.it\/wordpress\/come-caricare-i-dati-in-modo-asincrono-usando-i-loader-nello-sviluppo-di-applicazioni-android\/","title":{"rendered":"Come caricare i dati in modo asincrono usando i Loader nello sviluppo di applicazioni Android"},"content":{"rendered":"<p>Sappiamo che le <strong>applicazioni realizzate per Android<\/strong> devono essere il pi\u00f9 reattive possibile per massimizzare la <strong>user experience<\/strong>. Ci\u00f2 viene realizzato con le cosiddette operazioni <em>asincrone<\/em> nelle quali un&#8217;attivit\u00e0 che pu\u00f2 rivelarsi pi\u00f9 lenta viene svolta in background, su un thread secondario, una sorta di filone di esecuzione parallelo\u00a0rispetto a quello in cui opera l&#8217;interfaccia utente.<\/p>\n<p>Per non costringere i programmatori a dover fronteggiare una tematica ostica come i thread e le vie con cui essi comunicano con il resto dell&#8217;applicazione, Android ha messo a disposizione una classe apposita, AsyncTask, che al suo interno contiene <em>doInBackground<\/em> un metodo che lavora su un thread secondario (su cui svolgere le operazioni lente) ed altri metodi che si occupano di dialogare con il thread principale.<\/p>\n<p>Tra le varie operazioni &#8220;lente&#8221;, annoveriamo tutte le forme di caricamento dati che vanno dal parsing di un file XML al\u00a0JSON \u00a0fino alle query svolte su database. Per svolgere caricamenti asincroni, esiste una classe idonea, <strong>AsyncTaskLoader<\/strong>: la logica di funzionamento \u00e8 simile a quella di AsyncTask ma in questo caso siamo coadiuvati da un componente che gestisce il nostro lavoro, il LoaderManager.<\/p>\n<h2>L&#8217;esempio: i dati da caricare<\/h2>\n<p>Per creare un Loader dobbiamo estendere la classe AsyncTaskLoader&lt;E&gt; specificando il tipo di risultato che verr\u00e0 offerto. Noi faremo un esperimento con un Loader costruito attorno ad un database Sqlite: in pratica, quello che faremo sar\u00e0 caricare i risultati di una query in background. Vale la pena ancora sottolineare che tutto ci\u00f2 che faremo sar\u00e0 applicabile a qualsiasi altro tipo di caricamento dati.<\/p>\n<p>Prima di tutto vediamo il nostro database. Si tratta di un esempio\u00a0di prova, semplicissimo, costituito da una sola tabella con due campi. Serve a memorizzare un messaggio di log in cui sar\u00e0 registrato l&#8217;ora in cui si \u00e8 fatto click su un pulsante.<\/p>\n<p>Questo il nostro <em>helper<\/em> per gestire creazione e richiamo del database:<\/p>\n<pre class=\"lang:java decode:true \">public class DbHelper extends SQLiteOpenHelper{\r\n\r\n    public DbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {\r\n        super(context, name, factory, version);\r\n    }\r\n\r\n    @Override\r\n    public void onCreate(SQLiteDatabase db) {\r\n        String sql=\"CREATE TABLE messaggi (_id INTEGER PRIMARY KEY, messaggio TEXT)\";\r\n        db.execSQL(sql);\r\n    }\r\n\r\n    @Override\r\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\r\n\r\n    }\r\n}<\/pre>\n<p>La classe Logger invece sfrutter\u00e0 l&#8217;helper per salvare dati nel database e svolgere le query:<\/p>\n<pre class=\"lang:java decode:true\">public class Logger {\r\n\r\n    private DbHelper helper;\r\n\r\n    public Logger(Context ctx)\r\n    {\r\n        helper=new DbHelper(ctx,\"LogDB\",null,1);\r\n    }\r\n\r\n    public Cursor messaggi()\r\n    {\r\n        SQLiteDatabase db= helper.getReadableDatabase();\r\n        return db.query(\"messaggi\",null,null,null,null,null,null);\r\n    }\r\n\r\n    public boolean nuovoMessaggio(String s)\r\n    {\r\n        SQLiteDatabase db= helper.getWritableDatabase();\r\n        ContentValues cv=new ContentValues();\r\n        cv.put(\"messaggio\", s);\r\n        try {\r\n            db.insert(\"messaggi\", null, cv);\r\n        }catch(SQLiteException se)\r\n        {\r\n            return false;\r\n        }\r\n        return true;\r\n    }\r\n}<\/pre>\n<h2>Realizziamo il layout<\/h2>\n<p>Il layout sar\u00e0 molto semplice: costituito da un pulsante ed una ListView ad ogni click salver\u00e0 l&#8217;ora corrente su database e la lista mostrer\u00e0 tutti i log salvati sinora.<\/p>\n<p>Questa la sua descrizione XML (file: \/res\/layout\/activity_main.xml):<\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;LinearLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    android:orientation=\"vertical\"&gt;\r\n\r\n    &lt;Button\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:text=\"Aggiorna\"\r\n        android:onClick=\"aggiorna\"\/&gt;\r\n\r\n    &lt;ListView\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"match_parent\"\r\n        android:id=\"@+id\/lista\"\/&gt;\r\n\r\n&lt;\/LinearLayout&gt;<\/pre>\n<p>Ed ecco il suo aspetto:<\/p>\n<p><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/03\/android-loader-caricamento-dati-asincrono_img_01.jpg\" rel=\"attachment wp-att-11910\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-11910\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/03\/android-loader-caricamento-dati-asincrono_img_01.jpg\" alt=\"android-loader-caricamento-dati-asincrono_img_01\" width=\"330\" height=\"386\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/03\/android-loader-caricamento-dati-asincrono_img_01.jpg 330w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/03\/android-loader-caricamento-dati-asincrono_img_01-256x300.jpg 256w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/03\/android-loader-caricamento-dati-asincrono_img_01-300x351.jpg 300w\" sizes=\"auto, (max-width: 330px) 100vw, 330px\" \/><\/a><\/p>\n<h2>Loader e LoaderManager<\/h2>\n<p>Ora che sono stati chiariti tutti gli aspetti preliminari possiamo concentrarci sugli aspetti salienti dell&#8217;esempio. Il vantaggio di <strong>usare un Loader<\/strong> \u00e8 che non solo permette di caricare i dati in maniera asincrona ma si lega al ciclo di vita dell&#8217;Activity (o del Fragment) in cui viene usato ed in casi come la rotazione del dispositivo ripropone i risultati dell&#8217;ultimo caricamento senza svolgere di nuovo l&#8217;operazione. Con il <strong>LoaderManager<\/strong> avvieremo il nostro Loader &#8211; ad esempio nell&#8217;<em>onCreate<\/em> dell&#8217;app &#8211; mentre con un oggetto listener che estende <strong>LoaderCallbacks<\/strong> verranno gestite le principali fasi di vita del Loader:<\/p>\n<ul>\n<li>inizializzazione<\/li>\n<li>trattamento dei risultati restituiti<\/li>\n<li>reset<\/li>\n<\/ul>\n<p>Vediamo subito come \u00e8 fatto il nostro Loader:<\/p>\n<pre class=\"\">public class DbLoader extends AsyncTaskLoader&lt;Cursor&gt; {\r\n\r\n    private Logger logger;\r\n    private Cursor results;\r\n\r\n    public DbLoader(Context context, Logger l) {\r\n        super(context);\r\n        logger=l;\r\n    }\r\n\r\n    @Override\r\n    protected void onStartLoading() {\r\n        if (results != null) {\r\n            deliverResult(results);\r\n        }\r\n        if (takeContentChanged() || results == null) {\r\n            forceLoad();\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public Cursor loadInBackground() {\r\n        return logger.messaggi();\r\n    }\r\n\r\n    @Override\r\n    public void deliverResult(Cursor c) {\r\n         if (isReset()) {\r\n             if (c != null) {\r\n                 c.close();\r\n             }\r\n             return;\r\n         }\r\n         Cursor saved = results;\r\n         results = c;\r\n\r\n         if (isStarted()) {\r\n            super.deliverResult(c);\r\n         }\r\n\r\n         if (saved != null &amp;&amp; !saved.isClosed()) {\r\n             saved.close();\r\n         }\r\n     }\r\n\r\n}<\/pre>\n<p>Sul nostro Loader notiamo innanzitutto due cose: la classe estende AsyncTaskLoader&lt;Cursor&gt; con cui\u00a0dichiariamo che il tipo di dato trattato \u00e8 il Cursor; il metodo <em>loadInBackground<\/em>\u00a0che svolge asincronamente il lavoro &#8220;pesante&#8221;, in pratica esegue la query sul database. Il Loader conserva ogni Cursor recuperato dalle query nella propriet\u00e0 results\u00a0e la user\u00e0 come una sorta di cache.<\/p>\n<p>Tale cache viene gestita, in primo luogo, nel metodo <em>onStartLoading<\/em> dove se c&#8217;\u00e8 un risultato disponibile viene immediatamente restituito. Qui, si nota subito l&#8217;uso di tre metodi importanti in questo contesto: <em>forceLoad<\/em> costringe il Loader ad eseguire nuovamente il metodo <em>loadInBackground<\/em> per recuperare una nuova copia dei dati; <em>takeContentChanged<\/em> verifica se l&#8217;ultima lettura dei dati \u00e8 ancora valida o \u00e8 intercorsa qualche modifica al database (restituir\u00e0 true\u00a0se si \u00e8 invocato nel frattempo <em>onContentChanged<\/em>); <em>deliverResult<\/em> \u00e8 il metodo che si occupa di restituire i dati dal Loader all&#8217;Activity principale.<\/p>\n<p>Proprio in <em>deliverResult<\/em> passa il Cursor ottenuto subito prima di essere restituito: in questo caso, ci preoccupiamo di salvarne un riferimento in results\u00a0&#8211; per cache &#8211; e di chiudere il vecchio cursore.<\/p>\n<p>Nel codice dell&#8217;Activity possiamo invece riconoscere il ruolo del LoaderManager:<\/p>\n<pre class=\"lang:java decode:true\">public class MainActivity extends AppCompatActivity {\r\n\r\n    private CursorAdapter adapter;\r\n    private ListView list;\r\n    private SimpleDateFormat format=new SimpleDateFormat(\"HH:mm:ss\");\r\n    private Logger logger;\r\n    private final static int LOADER_ID=1000;\r\n\r\n    private LoaderManager.LoaderCallbacks&lt;Cursor&gt; callbacks=\r\n            new LoaderManager.LoaderCallbacks&lt;Cursor&gt;() {\r\n                @Override\r\n                public Loader&lt;Cursor&gt; onCreateLoader(int id, Bundle args) {\r\n                    return new DbLoader(MainActivity.this, logger);\r\n                }\r\n\r\n                @Override\r\n                public void onLoadFinished(Loader&lt;Cursor&gt; loader, Cursor data) {\r\n                    adapter.changeCursor(data);\r\n                }\r\n\r\n                @Override\r\n                public void onLoaderReset(Loader&lt;Cursor&gt; loader) {\r\n                    adapter.changeCursor(null);\r\n                }\r\n            };\r\n\r\n\r\n    public void aggiorna(View v)\r\n    {\r\n        String msg=\"Ultimo tic: \"+format.format(new Date());\r\n\r\n        if (logger.nuovoMessaggio(msg))\r\n            getSupportLoaderManager().getLoader(LOADER_ID).onContentChanged();\r\n\r\n    }\r\n\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n        setContentView(R.layout.activity_main);\r\n\r\n        logger=new Logger(this);\r\n\r\n        list= (ListView) findViewById(R.id.lista);\r\n\r\n        adapter=new SimpleCursorAdapter(this,android.R.layout.simple_list_item_1, null,\r\n                new String[]{\"messaggio\"}, new int[]{android.R.id.text1}, 0);\r\n\r\n        list.setAdapter(adapter);\r\n\r\n        getSupportLoaderManager().initLoader(LOADER_ID, null, callbacks);\r\n    }\r\n\r\n\r\n}<\/pre>\n<p>All&#8217;interno dell&#8217;<em>onCreate<\/em>, chiediamo al LoaderManager di inizializzare un Loader il cui caricamento ci viene notificato nel metodo <em>onLoadFinished<\/em> dei LoaderCallbacks. Ogni volta che viene cliccato il pulsante presente nell&#8217;interfaccia verr\u00e0 salvato un nuovo valore e si invocher\u00e0 il metodo <em>onContentChanged<\/em> per avvisare il Loader che i dati devono essere ricaricati dal database.<\/p>\n<p>L&#8217;uso dei Loader pu\u00f2 sembrare complesso, ma in realt\u00e0 una volta presa confidenza con il concetto tutto appare pi\u00f9 chiaro: si deve ricordare per lo pi\u00f9 che in <em>loadInBackground<\/em> si fa il lavoro &#8220;pesante&#8221; ed il LoaderManager gestisce il caricamento mentre tutti gli altri metodi completano il meccanismo e ne curano l&#8217;efficienza.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sappiamo che le applicazioni realizzate per Android devono essere il pi\u00f9 reattive possibile per massimizzare la user&#8230;<\/p>\n","protected":false},"author":561,"featured_media":12324,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1682,1],"tags":[1734,1733,1562,641],"class_list":["post-11909","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","category-tutorial-pratici","tag-android-loader","tag-asynctaskloader","tag-creare-app-android","tag-programmazione-android"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/11909","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/users\/561"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/comments?post=11909"}],"version-history":[{"count":9,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/11909\/revisions"}],"predecessor-version":[{"id":12325,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/11909\/revisions\/12325"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/media\/12324"}],"wp:attachment":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/media?parent=11909"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/categories?post=11909"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/tags?post=11909"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}