Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Android Build a Weather App (2015) Hooking Up the Model to the View Adding a Refresh Button

Fola Ogunfemi
Fola Ogunfemi
3,355 Points

Callback Error. OnResponse & OnFailure, not functional

I am unable to update the weather data on the app. The video has OnFailure and OnRepsonse written the way I do here, but I am receiving an error that I need to update their parameters in order ti run the code.

I have the parameters that are suggested by the IDE commented out in OnFailure and OnRepsonse. When I use these inputs, the data in the app does not update. When I don't, the app does not launch at all.

import android.content.Context; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.support.v4.content.ContextCompat; import android.support.v4.content.res.ResourcesCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast;

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

import java.io.IOException;

import butterknife.BindView; import butterknife.ButterKnife; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response;

//Perform network functions outside of the main thread in an asynchronous thread. Main thread should be reserved for UI in order to ensure that the app is responsive to input at any time, even if some networking functions are still waiting to be completed.

public class MainActivity extends AppCompatActivity {

public static final String TAG = MainActivity.class.getSimpleName();

// private Response response;

private CurrentWeather mCurrentWeather;

//Setting member variable views here equal to the resources in layouts using ButterKnife @BindView(R.id.timeLabel) TextView mTimeLabel; @BindView(R.id.temperatureLabel) TextView mTemperatureLabel; @BindView(R.id.humidityValue) TextView mHumidityValue; @BindView(R.id.precipValue) TextView mPrecipValue; @BindView(R.id.summaryLabel) TextView mSummaryLabel; @BindView(R.id.iconImageView) ImageView mIconImageView; @BindView(R.id.refreshImageView) ImageView mRefreshImageView; //Added OnClickListener to have this imageView function like a button @BindView(R.id.progressBar) ProgressBar mProgressBar; //Will superimpose the progressbar on top of the refresh button. It will show only when we get new data

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);  //attaches layout to THIS activity
    ButterKnife.bind(this);

    mProgressBar.setVisibility(View.INVISIBLE); //Will superimpose the progressbar on top of the refresh button. It will show only when we get new data


    //Forecast Latitude and Longitude
    final double latitude = 37.8267;
    final double longitude = -122.4233;



    mRefreshImageView.setOnClickListener(  new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            getForecast(latitude, longitude);
        }
    }  );

/* //This BoilerPlate action below can also be performed by ButterKnife bind statements as seen above private TextView mTemperatureLabel; mTemperatureLabel = (TextView)findViewById(R.id.temperatureLabel);*/

    //Below...Check to see that a network connection is available before making a network call
        //If true, run http request
        //If false, notify user via a Toast

    getForecast(latitude, longitude);

    Log.d(TAG, "Main UI Code is Running!");
}

private void getForecast( double latitude,double longitude) {
    String apiKey = "f5d3eda07bc28ebd39b109bd0a11d91b";

    String forecastUrl = "https://api.darksky.net/forecast/" + apiKey+ "/" + latitude + "," + longitude; // Full Sample URL for Alcatraz Weather. We have taken out the API Key and latitude longitude and set them to variables..."https://api.darksky.net/forecast/f5d3eda07bc28ebd39b109bd0a11d91b/37.8267,-122.4233"

    //Check to see that a network connection is available before making a network call
    if (isNetworkAvailable()) {

        toggleRefresh();

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(forecastUrl)
                .build();

        Call call = client.newCall(request);
        //Use callback as the main line of communication between the main thread and the background worker thread(asynchronous thread).
        // After the worker has performed, If there is an issue, the onFailure method will be called and if the callback is valid then the onResponse will be called

        call.enqueue(new Callback() {

            @Override
            public void onFailure(/*Call call*/Request request, IOException e) {

             //Refresh the data...Must be run on UI thread.
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        toggleRefresh();
                    }

                });
                alertUserAboutError();
            }

            @Override
            public void onResponse(/*Call call, */Response response) throws IOException {
                //Needed to request internet connection permission in the Android Manifest

                //Refresh the data...Must be run on UI thread.
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        toggleRefresh();
                    }
                });

                try {
                    String jsonData = response.body().string();
                    Log.v(TAG, jsonData);
                    if (response.isSuccessful()) {
                     //   Log.v(TAG, response.body().string());
                        mCurrentWeather = getCurrentDetails(jsonData);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                updateDisplay();
                            }
                        });

/*updating display would ordinarily be sufficient, but in this case we must us runOnUiThread, bc updateDisplay would be a background process and only the main UI thread can update. updateDisplay(); */

                    } else {
                        alertUserAboutError();
                    }
                } catch (IOException e) {
                    Log.e(TAG, "Exception caught: ", e);
                } catch (JSONException e) {
                    Log.e(TAG, "Exception caught: ", e);
                }

            }
        }); //end of callback


    } //end of network check conditional
     else {
        Toast.makeText(this, R.string.network_unavailable_message, Toast.LENGTH_LONG).show(); //Test network issues with airplane mode in emulator
    }
}

private void toggleRefresh() { //called for each onFailure, OnResponse and networkIsAvailable
    if (mProgressBar.getVisibility() == View.INVISIBLE) {
        mProgressBar.setVisibility(View.VISIBLE); //Will superimpose the progressbar on top of the refresh button. It will show only when we get new data
        mRefreshImageView.setVisibility(View.INVISIBLE); //Refresh will be invisible when progress is visible
    }
    else {
        mProgressBar.setVisibility(View.INVISIBLE);
        mRefreshImageView.setVisibility(View.VISIBLE);
    }
}

//NOTE: ONLY MAIN THREAD IS ALLOWED TO UPDATE USER INTFERFACE...
   //We use ACTIVITY.runOnUIThread( runnable object) to tell the main UI thread that we have code ready for it.
private void updateDisplay() {
    mTemperatureLabel.setText(mCurrentWeather.getTemperature() + "");
    mTimeLabel.setText("At " + mCurrentWeather.getFormattedTime() + " it will be");
    mHumidityValue.setText(mCurrentWeather.getHumidity() + "");
    mPrecipValue.setText(mCurrentWeather.getPrecipChance() + "%");
    mSummaryLabel.setText(mCurrentWeather.getSummary());

/* //getResources is deprecated. Drawable drawable = getResources().getDrawable(mCurrentWeather.getIconId()); mIconImageView.setImageDrawable(drawable);*/

/* Drawable drawable = ResourcesCompat.getDrawable(getResources(), mCurrentWeather.getIconId(), null); mIconImageView.setImageDrawable(drawable);*/

    Drawable drawable = ContextCompat.getDrawable(this, mCurrentWeather.getIconId());
    mIconImageView.setImageDrawable(drawable);
}

private CurrentWeather 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");

     CurrentWeather currentWeather = new CurrentWeather();

    currentWeather.setHumidity(currently.getDouble("humidity"));
    currentWeather.setTime(currently.getLong("time"));
    currentWeather.setIcon(currently.getString("icon"));
    currentWeather.setPrecipChance(currently.getDouble("precipProbability"));
    currentWeather.setSummary(currently.getString("summary"));
    currentWeather.setTemperature(currently.getDouble("temperature"));
    currentWeather.setTimeZone(timezone);

    //display the formatted time in the blog, as it should appear in the app
    Log.d(TAG, currentWeather.getFormattedTime());
    return new CurrentWeather();
}

private boolean isNetworkAvailable() {
    ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); //parameter is the name of service we want in either string format or referenced through the context CLASS as seen.

    NetworkInfo networkInfo = manager.getActiveNetworkInfo();
    //this also needs permission in the Android Manifest

   boolean isAvailable = false;
   if (networkInfo !=null && networkInfo.isConnected()){
       isAvailable = true;
   }
   return isAvailable;
}

private void alertUserAboutError() {
    AlertDialogFragment dialog = new AlertDialogFragment();

    dialog.show(getFragmentManager(), "error_dialog");  //this will show dialog and assigning a string tag to it
}

}

1 Answer

Seth Kroger
Seth Kroger
56,405 Points

At the end of getCurrentDetails() you are returning a brand new, empty CurrentWeather instead of the CurrentWeather you filled in with the data you got from the API. If you fix that and use the updated method signatures in onResponse/onFailure you should be fine

Fola Ogunfemi
Fola Ogunfemi
3,355 Points

Thanks.

It work with "Call call" set as parameters for OnFailure and OnResponse as opposed to the Request and Response paramters that we were instructed to use in the tutorial.

Would you happen to know why this was the case?