DevCorner: estraiamo i dati dal JSON [METEO]

Roberto Orgiu
Roberto Orgiu
DevCorner: estraiamo i dati dal JSON [METEO]

Nella scorsa puntata del nostro DevCorner, abbiamo visto come effettuare una richiesta da OpenWeatherMap, fermandoci una volta ricevuta la risposta in formato JSON: nell'episodio odierno, ci cimenteremo invece in un task relativamente semplice, ma che ci offre parecchi spunti per imparare nuovi trucchi e strumenti del mestiere.

L'idea di base è di prendere l'oggetto di tipo JSON, trasformarlo in qualcosa di più malleabile e trasferirlo all'Activity chiamante, dove ci occuperemo, in un prossimo tutorial, di sistemare per bene le informazioni sullo schermo.

LEGGI ANCHE: SwipeRefreshLayout e pulsante di condivisione

 I passi da fare per portare a termine la nostra missione sono, sostanzialmente, solo 3:

  1. parsificare il JSON
  2. creare un oggetto che contenga i dati necessari
  3. ritornare questo oggetto all'Activity chiamante

Per quanto riguarda il primo punto, la logica è in realtà molto semplice e si basa sui concetti espressi dall'immagine qui sotto (cortesia dei colleghi di AndroidHive) e dalla risposta che ritorna OpenWeatherMap

json_format

Il nostro codice sarà quindi molto lineare e affine alla struttura dell'oggetto JSON che abbiamo ricevuto:

JSONObject responseObject = new JSONObject(jsonData);
JSONObject weatherObject = responseObject.getJSONArray("weather").getJSONObject(0);
JSONObject mainObject = responseObject.getJSONObject("main");
String weatherMain = weatherObject.getString("main");
String weatherDescription = weatherObject.getString("description");
String weatherIconName = weatherObject.getString("icon");
double weatherTemperature = mainObject.getDouble("temp");
double weatherHumidity = mainObject.getDouble("humidity");
int iconRes = getResources().getIdentifier(String.format("ic_%s",weatherIconName), "drawable", getPackageName());

Come possiamo notare, si tratta semplicemente di estrarre gli oggetti in maniera discendente, in modo da poter inizializzare solamente le variabili di cui abbiamo bisogno e che andremo ad inserire in un oggetto di tipo WeatherData.

In questa classe abbiamo utilizzato due tecnologie: la prima è inerente il paradigma del Builder, che ci permette di evitare costruttori con lunghi parametri e accodare invece lunghe richieste, ritornando un oggetto del tipo specificato una volta invocato il metodo che abbiamo chiamato build().

Come funziona nel dettaglio? All'interno della classe WeatherData, abbiamo creato solamente costruttori privati, in modo da prevenire l'inizializzazione diretta dell'oggetto (il Builder sarebbe stato inutile altrimenti). Come sottoclasse interna, abbiamo creato quindi l'oggetto Builder in maniera statica, in quanto non abbiamo alcun bisogno di un'istanza di WeatherData per svolgere le nostre operazioni e, al contrario, sarà il Builder a fornirci l'oggetto di tipo WeatherData con i dati inseriti.

I vari getter di Builder sono molto semplici e ritornano tutti un valore dello stesso tipo della classe: avendo Builder come valore di ritorno, possiamo accodare le chiamate come succede nel codice qui sotto mentre, invocando il metodo build(), le variabili che abbiamo salvato dentro il nostro oggetto Builder verranno usate per invocare il costruttore privato di WeatherData.

WeatherData.Builder builder = new WeatherData.Builder();
builder.setCondition(weatherMain)
                    .setDescription(weatherDescription)
                    .setIconRes(iconRes)
                    .setTemperature(weatherTemperature)
                    .setHumidity(weatherHumidity);
return builder.build();

Una volta ottenuto un oggetto di tipo WeatherData, dobbiamo riuscire a fare in modo di passarlo all'Activity chiamante. Per fare questa operazione, abbiamo diverse strade, ma quella che abbiamo deciso di percorrere è l'implementazione di Parcelable: al contrario dell'interfaccia Serializable, che è davvero immediata nello sviluppo ma decisamente lenta nell'esecuzione, questa tecnologia richiederà un po' più di lavoro da parte nostra ma sarà decisamente più rapida durante l'esecuzione della nostra app, in quanto la reflection verrà utilizzata solamente per ritrovare il CREATOR e non per accedere a tutti i campi, come succederebbe con Serializable.

La prima cosa da fare è quindi implementare l'interfaccia Parcelable ed i due metodi richiesti dalla classe. Prima di scrivere la logica dei due metodi, ci conviene però creare un costruttore privato che prenda in input solamente un Parcel e da cui andremo a leggere, in un preciso ordine i valori contenuti in questo oggetto

private WeatherData(Parcel in) {
        mCondition = in.readString();
        mDescription = in.readString();
        mIconRes = in.readInt();
        mTemperature = in.readDouble();
        mHumidity = in.readDouble();
    }

Ora, andremo a scrivere la logica dei due metodi richiesti, lasciando 0 come valore di ritorno di describeContents() (non abbiamo bisogno di modificarlo) e andando invece a scrivere, all'interno di writeToParcel(), i nostri dati, avendo cura di scriverli nello stesso ordine in cui andremo a leggerli, altrimenti il nostro oggetto non riuscirà a recuperare correttamente i dati dalla memoria

@Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(mCondition);
        parcel.writeString(mDescription);
        parcel.writeInt(mIconRes);
        parcel.writeDouble(mTemperature);
        parcel.writeDouble(mHumidity);
    }

Abbiamo accennato poco fa all'esistenza di CREATOR: grazie a quest'oggetto, da cui andremo a richiamare il costruttore privato creato poco fa, il sistema è in grado di recuperare i nostri dati: diamo subito un'occhiata al codice per creare questa variabile, che è davvero molto semplice e diretto.

public static final Creator CREATOR = new Creator() {
        @Override
        public WeatherData createFromParcel(Parcel parcel) {
            return new WeatherData(parcel);
        }

        @Override
        public WeatherData[] newArray(int size) {
            return new WeatherData[size];
        }
    };

Ora, possiamo utilizzare il nostro oggetto come semplice Extra nelle chiamate ad Intent.putExtra() e recuperarlo nell'Activity tramite una chiamata ad Intent.getParcelableExtra(), che ci ritornerà un oggetto di tipo Parcelable cui andremo a castare il tipo WeatherData.

Siamo giunti alla fine di questo episodio e, come di consueto, abbiamo aggiornato i sorgenti sul repository GitHub. In caso di dubbi, incertezze o stati d'animo inquieti, vi aspettiamo sul forum per discuterne tutti insieme.