Una delle tecnologie alla base del web 2.0 è sicuramente il linguaggio di programma javascript utilizzabile all’interno delle pagine web per effettuare chiamate asincrone verso il server (le famose chiamate ajax). Molto spesso le risposte di queste chiamate non sono nel classico formato xml, ma in un più leggero json. Questo formato è diffusissimo nel web, ma risulta sicuramente molto utile anche a noi programmatori Android. Vediamo oggi, nel nostro nuovo tutorial di programmazione Android, come creare un’applicazione che esegua chiamate verso un server (ad esempio quello di twitter) e che legga il risultato in formato json.
Cos’è il formato JSON
Il formato json è sostanzialmente la rappresentazione in javascript di un oggetto e può quindi essere facilmente interpretato all’interno di una pagina web. Rispetto al formato xml è più conciso e quindi viene usato spesso per generare un minor traffico di rete. Un esempio di oggetto json è il seguente:
1 2 3 4 5 6 7 | { blog: 'devapp', sezioni: [ {id: 1, nome: 'iOs'}, {id: 2, nome: 'Android'} ] } |
In javascript un oggetto è un insieme di coppie nome:valore, nel formato json le varie coppie sono racchiuse da parentesi graffe e le liste di oggetti sono fra parentesi quadre. Per ulteriori approfondimenti sull’argomento potete trovare molti tutorial in rete, io vi consiglio questo diviso in due parti (parte 1 e parte 2) per due motivi: uno perchè lo trovo molto interessante, due perchè l’ho scritto io :P.
Il formato json viene utilizzato spesso anche quando si crea un’infrastruttara REST (Representational State Transfer). In poche parole (anche se ci sono libri interi che parlano di REST!) REST è un tipo di architettura per servizi web che si basa sulla rappresentazione delle risorse. REST sfrutta al massimo il protocollo http, quindi molto leggero, ed è proprio questa una delle soluzioni ideali da usare nel caso in cui volessimo condividere dati con una applicazione mobile, che si tratti di Android, come vedremo in questo esempio, che di altri device mobili come iPhone o iPad. Ok, non sono riuscito a spiegare REST in due parole, se volete approfondire vi consiglio una lettura direttamente su wikipedia!
Effettuare una chiamata json
Passiamo al nostro esempio concreto. Vogliamo eseguire all’interno di un’applicazione Android una chiamata verso un server. Nello specifico vogliamo effettuare una ricerca su twitter per vedere chi parla di devAPP.
Per eseguire una chiamata a un server su internet dobbiamo creare una connessione verso un certo url, questa cosa può essere fatta usando la classe HttpURLConnection:
1 | HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
Una volta aperta la connessione dobbiamo leggere i dati. In java il concetto importante quando si tratta di manipolare un flusso di dati è quello di stream (input stream nel caso di lettura e output stream nel caso di scrittura). Uno stream di dati può essere qualunque cosa, per esempio un file su file system, ma anche dati letti da una connessione verso un server o ancora la console di una applicazione desktop (i famosi System.in e System.out).
Nel nostro caso dobbiamo ottenere lo stream dalla connessione:
1 | InputStream in = conn.getInputStream(); |
Una volta ottenuto lo stream lo dobbiamo scorrere e creare una stringa corrispondente alla risposta del server. Il modo più semplice per far questo è usare un BufferedReader e concatenare le stringhe in uno StringBuilder:
1 2 3 4 5 6 7 | StringBuilder sb = new StringBuilder(); BufferedReader r = new BufferedReader( new InputStreamReader(new DoneHandlerInputStream(in))); for (String line = r.readLine(); line != null; line = r.readLine()) { sb.append(line); } |
Come già detto gli input stream servono per leggere un flusso generico di dati (questo potrebbe contenere anche una bitmap o qualunque altra cosa). Nel nostro esempio abbiamo usato anche un reader (nel dettaglio un BufferedReader). Tutte le classi che estendono Reader permettono di leggere da un flusso di dati contenente caratteri.
Per evitare problemi in alcune versioni di Android dovute a un bug noto abbiamo usato la classe DoneHandlerInputStream, l’implementazione è la seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class DoneHandlerInputStream extends FilterInputStream { private boolean done; public DoneHandlerInputStream(InputStream stream) { super(stream); } @Override public int read(byte[] bytes, int offset, int count) throws IOException { if (!done) { int result = super.read(bytes, offset, count); if (result != -1) { return result; } } done = true; return -1; } } |
A questo punto possiamo usare la classe JSONObject, che si occuperà di eseguire il parsing della stringa:
1 | new JSONObject(sb.toString()); |
Il codice completo del metodo che crea un JSONObject partendo da un url è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private static JSONObject getJSONObject(String url) throws IOException, MalformedURLException, JSONException { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); InputStream in = conn.getInputStream(); try { StringBuilder sb = new StringBuilder(); BufferedReader r = new BufferedReader( new InputStreamReader(new DoneHandlerInputStream(in))); for (String line = r.readLine(); line != null; line = r.readLine()) { sb.append(line); } return new JSONObject(sb.toString()); } finally { in.close(); } } |
Ricerca su twitter effettuando una chiamata json
Twitter mette a disposizione vari url per interagire con i propri servizi. Uno dei più utilizzati è quello che permette di effettuare una ricerca, provate a andare a questo url. Come potete vedere nell’url è presente la parola da cercare (in questo caso “devapp”), il risultato è in formato json e il campo results contiene la lista dei risultati della ricerca.
NOTA: La documentazione del servizio mostra nel dettaglio tutti i parametri che è possibile passare.
Ok, cerchiamo ora di rimettere insieme tutti i pezzi: vediamo come costruire un blocco di codice che esegua una ricerca su twitter e stampi a console i testi dei tweet che parlano di devAPP:
1 2 3 4 5 6 7 | JSONObject obj = getJSONObject("http://search.twitter.com/search.json?q=devapp"); JSONArray jsonArray = obj.getJSONArray("results"); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); System.out.println(jsonObject.getString("text")); } |
Come già detto il campo results contiene una lista di oggetti con i risultati della nostra ricerca. All’interno del ciclo for con il metodo getJSONObject ricaviamo l’i-esimo elemento di questa lista. Su questo oggetto possiamo accedere a tutti i dati di un singolo tweet, in questo caso stampiamo il testo ricavato invocando il metodo getString(“text”).
Visto che stiamo parlando di Android la cosa più naturale da fare è mettere i risultati della ricerca in una ListView. Il modo più semplice per realizzarla è utilizzare un ArrayAdapter e mostrare i dati usando un layout android.R.layout.simple_list_item_1 per ogni singola riga in questo modo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1); setListAdapter(adapter); try { JSONObject obj = getJSONObject( "http://search.twitter.com/search.json?q=devapp"); JSONArray jsonArray = obj.getJSONArray("results"); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); adapter.add(jsonObject.getString("text")); } } catch (IOException ignored) { } catch (JSONException ignored) { } } |
Come già discusso in altri post una chiamata a un server internet non dovrebbe essere fatta nel thread principale dell’applicazione, ma in un thread in background usando la classe AsyncTask. Ecco il nostro esempio rivisto per usare un AsyncTask:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1); setListAdapter(adapter); new AsyncTask<Void, String, Void>() { @Override protected Void doInBackground(Void... params) { try { JSONObject obj = getJSONObject( "http://search.twitter.com/search.json?q=devapp"); JSONArray jsonArray = obj.getJSONArray("results"); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); publishProgress(jsonObject.getString("text")); } } catch (IOException ignored) { } catch (JSONException ignored) { } return null; } @Override protected void onProgressUpdate(String... values) { for (String tweet : values) { adapter.add(tweet); } } }.execute(); } |
Ecco il risultato finale (la grafica non è proprio esaltante, possiamo fare meglio!):
Questo esempio utilizza una connessione ad internet, non dimenticatevi di aggiungere nel manifest l’apposita permission:
1 | <uses-permission android:name="android.permission.INTERNET" /> |
Conclusioni
L’esempio visto in questo post è fin troppo semplice (i sorgenti completi sono disponibili come al solito e potete scaricarli seguendo questo indirizzo) anche se per far concorrenza all’applicazione ufficiale di Twitter dovremo lavorare ancora molto! Nei prossimi post gestiremo meglio gli errori, aggiungeremo l’immagine dell’autore del tweet e faremo un altro po’ di cosette…
A presto!
Ciao, complimenti per i tutorial,sono utilissimi.
Ho una domanda:
se il testo che mi ritorna dalla chiamata json contiene tag html, come posso pulirlo da lcodice html o ancor meglio, visualizzarlo correttamente?
grazie in anticipo
Ciao, dipende da dove vuoi fare vedere il testo. Puoi usare una WebView o anche una semplice TextView stando attento a usare il metodo Html.fromHtml per impostare il testo
Fabio
Ciao Fabio, complimenti per i tuoi tutorial molto chiari e schietti 🙂
Mi saranno sicuramente utili per apprendere un po’ di programmazione Android in più!
Ciao fabio scusami, ho provato a lanciare l’app ma ottengo la schermata completamente nera nel senso che non visualizzio nulla a parte DevApp_twitterSearch. Mi potresti aiutare? Grazie