Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Android Android Lists and Adapters Updating the Data Model Introducing JSONArray

Android Lists and adapters AccuWeather example.

The following is an example on how to convert Stormy to use jsonHourlyData from the AccuWeather API in the Android Lists and adapters lesson. The original JSONDATA from the Dark Sky API included current weather data and the hourly forecast in a single data set. AccuWeather separates current weather and the hourly forecast into two separate data sets requiring two okhttp3 requests as opposed to one request.

A few things to take note to I keep private Current current; instead of changing to private Forecast forecast; I added

String hourlyForecast = "https://dataservice.accuweather.com/forecasts/v1/hourly/12hour/" + locationKey + "?apikey=" + apiKey + "&language=en-us&details=false&metric=false";

I did not use forecast = parseForecastData(jsonData); or Current current = forecast.getCurrent(); in the first request but instead used the original code current = getCurrentDetails(jsonData); I added the second okhttp3 request under the first.

   // building second request to get hourly data from AccuWeather
Request hourlyAccuWeather = new Request.Builder().url(hourlyForecast).build();
Call secondCall = client.newCall(hourlyAccuWeather);
secondCall.enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) {

    }

    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

        try {
            String jsonHourData = response.body().string();
            Log.v(TAG, jsonHourData);
            if (response.isSuccessful()) {
                parseForecastData(jsonHourData);
            } else {alertUserAboutError();}
        } catch (IOException e) {Log.e(TAG, "JSON Exception caught: ", e);}
             catch (JSONException e) {Log.e(TAG, "JSON Exception caught: ", e);}
    }
});

And I combined the data from the two classes into the Forecast class.

private Forecast parseForecastData(String jsonData) throws JSONException {
    Forecast forecast = new Forecast();

    forecast.setCurrent(current);  // not ‘getCurrentDetails(jsonData)’
    forecast.setHourlyForcast(getHourlyForcast(jsonData));

    return forecast;
}

I looped through the JSON Hourly data as follows remember the data is organized different then what is in the lesson.

private Hour[] getHourlyForcast(String jsonData) throws JSONException {
    JSONArray jsonArray = new JSONArray(jsonData);
    Hour[] hours = new Hour[jsonArray.length()];

    for(int i=0; i < jsonArray.length(); i++) {
        JSONObject row = jsonArray.getJSONObject(i);

        Hour hour = new Hour();
        hour.setSummary(row.getString("IconPhrase"));
        hour.setIcon(row.getInt("WeatherIcon"));
        hour.setTime(row.getLong("EpochDateTime"));
        hour.setTimeZone("America/New_York");

        JSONObject temperature = row.getJSONObject("Temperature");
        hour.setTemperature(temperature.getDouble("Value"));

        hours[i] = hour;
    }
    return hours;
}

Here is the entire MainActivity.java

package com.example.stormy.ui;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.stormy.R;
import com.example.stormy.weather.Current;
import com.example.stormy.databinding.ActivityMainBinding;
import com.example.stormy.weather.Forecast;
import com.example.stormy.weather.Hour;

import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import static java.util.TimeZone.getAvailableIDs;

public class MainActivity extends AppCompatActivity {
public static final String TAG = MainActivity.class.getSimpleName();
private Current current; // not ‘private Forecast forecast;’
private ImageView iconImageView;

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

    private void getForcast() {
        final ActivityMainBinding binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);

        TextView accuWeather = findViewById(R.id.accuWeatherAttribution);
        accuWeather.setMovementMethod(LinkMovementMethod.getInstance());

        iconImageView = findViewById(R.id.iconImageView);

        String apiKey = "yourApiKey";  // change this to your ApiKey
        String locationKey = "349727"; 
        String currentForecast = "https://dataservice.accuweather.com/currentconditions/v1/" + locationKey + "?apikey=" + apiKey + "&language=en-us&details=true";
        String hourlyForecast = "https://dataservice.accuweather.com/forecasts/v1/hourly/12hour/" + locationKey + "?apikey=" + apiKey + "&language=en-us&details=false&metric=false";


        if (isNetworkAvailable()) {
            OkHttpClient client = new OkHttpClient();

            Request currentAccuWeather = new Request.Builder()
                    .url(currentForecast)
                    .build();

            Call call = client.newCall(currentAccuWeather);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {

                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    try {
                        String jsonData = response.body().string();
                        Log.v(TAG, jsonData);
                        if (response.isSuccessful()) {

                                // don't use forecast = parseForecastData(jsonData); here we will set the current data later in the parseForecastData() method.
                                // don't use Current current = forecast.getCurrent(); here its not needed since we are not using the Forecast class yet.

                                current = getCurrentDetails(jsonData); // keep this original code.

                               final Current displayWeather = new Current(
                                        current.getLocationLabel(),
                                        current.getIcon(),
                                        current.getTime(),
                                        current.getTemperature(),
                                        current.getHumidity(),
                                        current.getPrecipChance(),
                                        current.getSummary(),
                                        current.getTimeZone()
                                );
                                binding.setWeather(displayWeather);

                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        Drawable drawable = getResources().getDrawable(displayWeather.getIconId());
                                        iconImageView.setImageDrawable(drawable);
                                    }
                                });
                        } else {
                            alertUserAboutError();
                        }
                    } catch (IOException e) {
                        Log.v(TAG, "IO Exception caught: ", e);
                    } catch (JSONException e) {
                        Log.e(TAG,"JSON Exception caught ", e);
                    }
                }
            });

                // building second request to get hourly data from AccuWeather
            Request hourlyAccuWeather = new Request.Builder().url(hourlyForecast).build();
            Call secondCall = client.newCall(hourlyAccuWeather);
            secondCall.enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {

                }

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

                    try {
                        String jsonHourData = response.body().string();
                        Log.v(TAG, jsonHourData);
                        if (response.isSuccessful()) {
                            parseForecastData(jsonHourData);
                        } else {alertUserAboutError();}
                    } catch (IOException e) {Log.e(TAG, "JSON Exception caught: ", e);}
                         catch (JSONException e) {Log.e(TAG, "JSON Exception caught: ", e);}
                }
            });
        }
    }
    private Forecast parseForecastData(String jsonData) throws JSONException {
        Forecast forecast = new Forecast();

        forecast.setCurrent(current);  // not ‘getCurrentDetails(jsonData)’
        forecast.setHourlyForcast(getHourlyForcast(jsonData));

        return forecast;
    }

    private Hour[] getHourlyForcast(String jsonData) throws JSONException {
        JSONArray jsonArray = new JSONArray(jsonData);
        Hour[] hours = new Hour[jsonArray.length()];

        for(int i=0; i < jsonArray.length(); i++) {
            JSONObject row = jsonArray.getJSONObject(i);

            Hour hour = new Hour();
            hour.setSummary(row.getString("IconPhrase"));
            hour.setIcon(row.getInt("WeatherIcon"));
            hour.setTime(row.getLong("EpochDateTime"));
            hour.setTimeZone("America/New_York");

            JSONObject temperature = row.getJSONObject("Temperature");
            hour.setTemperature(temperature.getDouble("Value"));

            hours[i] = hour;
        }
        return hours;
    }

    private Current getCurrentDetails(String jsonData) throws JSONException {
        JSONArray jsonArray = new JSONArray(jsonData);
        JSONObject row = jsonArray.getJSONObject(0);
        String LocalObservationDateTime = row.getString("LocalObservationDateTime");

        JSONObject temperature = row.getJSONObject("Temperature");
        JSONObject imperial = temperature.getJSONObject("Imperial");
        String value = imperial.getString("Value");

        Log.i(TAG,"From JSON: : " + LocalObservationDateTime);
        Log.i(TAG, "Temperature : " + value);

        Current current = new Current();
        current.setHumidity(row.getInt("RelativeHumidity"));
        current.setTime(row.getLong("EpochTime"));
        current.setIcon(row.getInt("WeatherIcon"));
        current.setLocationLabel("New York City, NY");
        current.setPrecipChance(row.getString("PrecipitationType"));
        current.setSummary(row.getString("WeatherText"));
        current.setTemperature(imperial.getDouble("Value"));
        current.setTimezone("America/New_York"); // US/Eastern

        Log.d(TAG, "Formatted Time: " + current.getFormattedTime());

        return current;
    }

    private boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();

        boolean isAvailable = false;
        if (networkInfo != null && networkInfo.isConnected()) {
            isAvailable = true;
        }
        else {
            Toast.makeText(this, R.string.network_unavailable_message, Toast.LENGTH_LONG).show();
        }
        return isAvailable;
    }

    private void alertUserAboutError() {
    AlertDialogFragment dialog = new AlertDialogFragment();
    dialog.show(getSupportFragmentManager(), "error_dialog");
    }

    public void refreshOnClick(View view) {
        Toast.makeText(this,"Refreshing Data", Toast.LENGTH_LONG).show();
        getForcast();
    }
}

Here is the Hour class some of the variables have different types required by the AccuWeather API

package com.example.stormy.weather;

public class Hour {
    private long time;
    private String summary;
    private double temperature;
    private int icon;
    private String timeZone;

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public double getTemperature() {
        return temperature;
    }

    public void setTemperature(double temperature) {
        this.temperature = temperature;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public String getTimeZone() {
        return timeZone;
    }

    public void setTimeZone(String timeZone) {
        this.timeZone = timeZone;
    }
}

And here is the Forecast class

package com.example.stormy.weather;

public class Forecast {

    private Current current;
    private Hour[] hourlyForcast;

    public Current getCurrent() {
        return current;
    }

    public void setCurrent(Current current) {
        this.current = current;
    }

    public Hour[] getHourlyForcast() {
        return hourlyForcast;
    }

    public void setHourlyForcast(Hour[] hourlyForcast) {
        this.hourlyForcast = hourlyForcast;
    }
}

And here is the Current class which also has variables with different data types due to the AccuWeather API but has not been modified since the previous Stormy Weather course.

package com.example.stormy.weather;

import com.example.stormy.R;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Current {

    private String locationLabel;
    private int icon;
    private long time;
    private double temperature;
    private int humidity;
    private String precipChance;
    private String summary;
    private String timeZone;

    public Current() {
    }

    public Current(String locationLabel, int icon, long time, double temperature, int humidity, String precipChance, String summary, String timeZone) {
        this.locationLabel = locationLabel;
        this.icon = icon;
        this.time = time;
        this.temperature = temperature;
        this.humidity = humidity;
        this.precipChance = precipChance;
        this.summary = summary;
        this.timeZone = timeZone;
    }

    public String getLocationLabel() {
        return locationLabel;
    }

    public void setLocationLabel(String locationLabel) {
        this.locationLabel = locationLabel;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public int getIconId() {
        int iconId = R.drawable.clear_day;

        switch (icon) {
            case 1:
                iconId = R.drawable.clear_day;
                break;
            case 33:
                iconId = R.drawable.clear_night;
                break;
            case 18:
                iconId = R.drawable.rain;
                break;
            case 22:
                iconId = R.drawable.snow;
                break;
            case 25:
                iconId = R.drawable.sleet;
                break;
            case 32:
                iconId = R.drawable.wind;
                break;
            case 11:
                iconId = R.drawable.fog;
                break;
            case 7:
                iconId = R.drawable.cloudy;
                break;
            case 6:
                iconId = R.drawable.partly_cloudy;
                break;
            case 35:
                iconId = R.drawable.cloudy_night;
                break;

        }
        return iconId;
    }

    public long getTime() {
        return time;
    }

    public String getTimeZone() {
        return timeZone;
    }

    public void setTimezone(String timeZone) {
        this.timeZone = timeZone;
    }

    public String getFormattedTime() {
        SimpleDateFormat formatter = new SimpleDateFormat("h:mm a");
        formatter.setTimeZone(TimeZone.getTimeZone(timeZone));
        Date dateTime = new Date(time * 1000);

        return formatter.format(dateTime);
    }

    public void setTime(long time) {
        this.time = time;
    }

    public double getTemperature() {
        return temperature;
    }

    public void setTemperature(double temperature) {
        this.temperature = temperature;
    }

    public int getHumidity() {
        return humidity;
    }

    public void setHumidity(int humidity) {
        this.humidity = humidity;
    }

    public String getPrecipChance() {
        return precipChance;
    }

    public void setPrecipChance(String precipChance) {
        this.precipChance = precipChance;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }
}