Android Android Lists and Adapters Connecting the Data Data & Performance Review

Jonathan Grieve
MOD
Jonathan Grieve
Treehouse Moderator 82,645 Points

[SOLVED] Stormy App - IllegalStateException on HourlyButtonClick method.

I'm almost there, and in fact, I've officially completed the course but I have one more problem to get over.

Once I've switched over the hourlyOnClick method to bind to the JSON data the hourly Button triggers an IllegalStateException with the following stack trace. I've spent a great deal of time and research on this but I'm afraid I'm just not good at interpreting and debugging Stacktrace Errors.

https://github.com/jg-digital-media/stormy_list_tth

07-19 11:25:01.303 13973-13973/uk.co.jonniegrieve.stormy E/AndroidRuntime: FATAL EXCEPTION: main
    Process: uk.co.jonniegrieve.stormy, PID: 13973
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:389)
        at android.view.View.performClick(View.java:5198)
        at android.view.View$PerformClick.run(View.java:21147)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384)
        at android.view.View.performClick(View.java:5198) 
        at android.view.View$PerformClick.run(View.java:21147) 
        at android.os.Handler.handleCallback(Handler.java:739) 
        at android.os.Handler.dispatchMessage(Handler.java:95) 
        at android.os.Looper.loop(Looper.java:148) 
        at android.app.ActivityThread.main(ActivityThread.java:5417) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
     Caused by: java.lang.NullPointerException: storage == null
        at java.util.Arrays$ArrayList.<init>(Arrays.java:38)
        at java.util.Arrays.asList(Arrays.java:155)
        at com.teamtreehouse.stormy.ui.MainActivity.hourlyOnClick(MainActivity.java:222)
        at java.lang.reflect.Method.invoke(Native Method) 
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384) 
        at android.view.View.performClick(View.java:5198) 
        at android.view.View$PerformClick.run(View.java:21147) 
        at android.os.Handler.handleCallback(Handler.java:739) 
        at android.os.Handler.dispatchMessage(Handler.java:95) 
        at android.os.Looper.loop(Looper.java:148) 
        at android.app.ActivityThread.main(ActivityThread.java:5417) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 


    --------- beginning of system
07-19 11:25:01.327 1228-1228/? E/EGL_emulation: tid 1228: eglCreateSyncKHR(1881): error 0x3004 (EGL_BAD_ATTRIBUTE)

No errors in the code... I really think it's something behind the scenes causing this. So near but yet so far. :)

mainActivity.java
package com.teamtreehouse.stormy.ui;

import android.content.Context;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.v7.app.AppCompatActivity;
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.teamtreehouse.stormy.R;
import com.teamtreehouse.stormy.Weather.Current;
import com.teamtreehouse.stormy.Weather.Forecast;
import com.teamtreehouse.stormy.Weather.Hour;
import com.teamtreehouse.stormy.databinding.ActivityMainBinding;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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

public class MainActivity extends AppCompatActivity {

  public static final String TAG = MainActivity.class.getSimpleName();
  private Forecast forecast;
  private ImageView iconImageView;

  double latitude = 37.8267;
  double longitude = -122.4233;

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

    getForecast(latitude, longitude);
    Log.d(TAG, "Main UI code is running, hooray!");
  }

  private void getForecast(double latitude, double longitude) {
    final ActivityMainBinding binding = DataBindingUtil
        .setContentView(MainActivity.this, R.layout.activity_main);

    iconImageView = findViewById(R.id.iconImageView);

    // Setup Dark Sky Link
    TextView darkSky = findViewById(R.id.darkSkyAttribution);
    darkSky.setMovementMethod(LinkMovementMethod.getInstance());

    String apiKey = "57eaf3aa961968bf65b0619680588073";


    String forecastURL = "https://api.darksky.net/forecast/" + apiKey +
        "/" + latitude + "," + longitude;

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

      Request request = new Request.Builder()
          .url(forecastURL)
          .build();

      Call call = client.newCall(request);
      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()) {

                forecast = parseForecastData(jsonData);

                Current current = forecast.getCurrent();

                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.e(TAG, "IO Exception caught: ", e);
          } catch (JSONException e) {
            Log.e(TAG, "JSON Exception caught: ", e);
          }

        }
      });
    }
    else {
      Toast.makeText(this, R.string.network_unavailable_message,
          Toast.LENGTH_LONG).show();
    }
  }

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

    forecast.setCurrent(getCurrentDetails(jsonData));

    return forecast;
  }

  private Hour[] getHourlyForecast(String jsonData) throws JSONException {
        JSONObject forecast = new JSONObject(jsonData);
        String timezone = forecast.getString("timezone");

       //get an array of JSON objects
        JSONObject hourly = forecast.getJSONObject("hourly");
        JSONArray data = hourly.getJSONArray("data");
        Hour[] hours = new Hour[data.length()];

        for (int i=0; i<data.length(); i++){
            JSONObject jsonHour = data.getJSONObject(i);

            Hour hour = new Hour();

            hour.setSummary(jsonHour.getString("summary"));
            hour.setIcon(jsonHour.getString("icon"));
            hour.setTemperature(jsonHour.getDouble("temperature"));
            hour.setTime(jsonHour.getLong("time"));
            hour.setTimeZone(timezone);

            hours[i] = hour;

        }
        return hours;

    }

  private Current getCurrentDetails(String jsonData) throws JSONException {
    JSONObject forecast = new JSONObject(jsonData);

    String timezone = forecast.getString("timezone");
    Log.i(TAG, "From JSON: " + timezone);

    JSONObject currently = forecast.getJSONObject("currently");

    Current current = new Current();

    // Parse weather data from currently object
    current.setHumidity(currently.getDouble("humidity"));
    current.setTime(currently.getLong("time"));
    current.setIcon(currently.getString("icon"));
    current.setLocationLabel("Alcatraz Island, CA");
    current.setPrecipChance(currently.getDouble("precipProbability"));
    current.setSummary(currently.getString("summary"));
    current.setTemperature(currently.getDouble("temperature"));
    current.setTimeZone(timezone);

    Log.d(TAG, 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;
    }

    return isAvailable;
  }

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

  public void refreshOnClick(View view) {
    getForecast(latitude, longitude);
    Toast.makeText(this, "Refreshing data", Toast.LENGTH_LONG).show();
  }

  public void hourlyOnClick(View view) {

      List<Hour> hours = Arrays.asList(forecast.getHourlyForecast());
      Intent intent = new Intent(this, HourlyForecastActivity.class);

      intent.putExtra("HourlyList", (Serializable) hours);
      startActivity(intent);
  }

}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name = "weather"
            type = "com.teamtreehouse.stormy.Weather.Current"/>
    </data>
    <android.support.constraint.ConstraintLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/appBackground"
        tools:context="com.teamtreehouse.stormy.ui.MainActivity">

        <TextView
            android:id="@+id/temperatureView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:text="@{String.valueOf(Math.round(weather.temperature)), default = `100`}"
            android:textColor="@android:color/white"
            android:textSize="150sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

        <ImageView
            android:id="@+id/degreeImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="35dp"
            app:layout_constraintStart_toEndOf="@+id/temperatureView"
            app:layout_constraintTop_toTopOf="@+id/temperatureView"
            app:srcCompat="@drawable/degree"/>

        <TextView
            android:id="@+id/timeValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:text="@{`At ` + String.valueOf(weather.formattedTime) + ` it will be`, default = `At 5:00 PM it will be`}"
            android:textColor="@color/half_white"
            android:textSize="18sp"
            app:layout_constraintBottom_toTopOf="@+id/temperatureView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

        <TextView
            android:id="@+id/locationValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="24dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:text="Alcatraz Island, CA"
            android:textColor="@android:color/white"
            android:textSize="24sp"
            app:layout_constraintBottom_toTopOf="@+id/timeValue"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

        <ImageView
            android:id="@+id/iconImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginStart="32dp"
            app:layout_constraintBottom_toBottomOf="@+id/locationValue"
            app:layout_constraintStart_toStartOf="parent"
            app:srcCompat="@drawable/cloudy_night"/>

        <android.support.constraint.Guideline
            android:id="@+id/guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.33"/>

        <android.support.constraint.Guideline
            android:id="@+id/guideline2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.66"/>

        <TextView
            android:id="@+id/humidityLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginTop="16dp"
            android:text="HUMIDITY"
            android:textColor="@color/half_white"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="@+id/guideline"
            app:layout_constraintTop_toBottomOf="@+id/temperatureView"/>

        <TextView
            android:id="@+id/humidityValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:text="@{String.valueOf(weather.humidity), default=`0.88`}"
            android:textColor="@android:color/white"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="@+id/humidityLabel"
            app:layout_constraintStart_toStartOf="@+id/humidityLabel"
            app:layout_constraintTop_toBottomOf="@+id/humidityLabel"/>

        <TextView
            android:id="@+id/precipLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginTop="15dp"
            android:text="RAIN/SNOW?"
            android:textColor="@color/half_white"
            app:layout_constraintEnd_toStartOf="@+id/guideline2"
            app:layout_constraintStart_toStartOf="@+id/guideline2"
            app:layout_constraintTop_toBottomOf="@+id/temperatureView"/>

        <TextView
            android:id="@+id/precipValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:text="@{String.valueOf(Math.round(weather.precipChance * 100)) + ` %`, default = `50 %`}"
            android:textColor="@android:color/white"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="@+id/precipLabel"
            app:layout_constraintStart_toStartOf="@+id/precipLabel"
            app:layout_constraintTop_toBottomOf="@+id/precipLabel"/>

        <TextView
            android:id="@+id/summaryValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:text="@{weather.summary, default = `Stormy with a chance of meatballs`}"
            android:textColor="@android:color/white"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/precipValue"/>

        <TextView
            android:id="@+id/darkSkyAttribution"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:text="@string/dark_sky_message"
            android:textColor="@color/half_white"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

        <ImageView
            android:id="@+id/refreshImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:onClick="refreshOnClick"
            app:layout_constraintBottom_toTopOf="@+id/locationValue"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/refresh"/>

        <Button
            android:id="@+id/hourlyButton"
            android:layout_width="wrap_content"
            android:layout_height="30dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:background="#40ffffff"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:textColor="@android:color/white"
            android:onClick="hourlyOnClick"
            android:text="Hourly Forecast"
            app:layout_constraintBottom_toTopOf="@+id/darkSkyAttribution"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/summaryValue"/>
    </android.support.constraint.ConstraintLayout>
</layout>
Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 82,645 Points

Found out the issue in the end. I'm still not good at reading Stacktraces but here's the StackOverflow threadat pointed me in the right direction. I wasn't returning the method properly because I wasn't actually parsing the Data in JSON format with the parse method.

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

    forecast.setCurrent(getCurrentDetails(jsonData));
    forecast.setHourlyForecast(getHourlyForecast(jsonData));

    return forecast;
  }