Vladimir Tumbev Vladimir Tumbev - 1 year ago 55
JSON Question

Android App using OpenWeatherMap crashes when user inputs invalid City, how to handle 404 code from API?

My app has weather implemented into it and it is using the data from OpenWeatherMap.

The user can input the city that they would like the weather for via an Alert Dialog code but the app crashes when they input an invalid city.

Which in turn changes the URL to:

http://api.openweathermap.org/data/2.5/weather?q={Invalid City Here} &units=metric&APPID={APP ID HERE}

and the API returns {"cod":"404","message":"city not found"}, which causes my app to crash!

My question is: How to handle that 404 code so that the app doesn't crash but notifies the user that they need to type in a valid City. - I apologise if this is a waste of time for you guys, but I am still learning and am quite new to all this...
More detailed explanations and non-condescending guidelines would be very appreciated and would help me learn!

The alert dialog is below:

private void showChangeCityDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(R.string.change_city_title);

final EditText cityInput = new EditText(MainActivity.this);
cityInput.setInputType(InputType.TYPE_CLASS_TEXT);
cityInput.setHint("Sofia,BG");
builder.setView(cityInput);
builder.setPositiveButton(R.string.submit, new DialogInterface.OnClickListener() {


@Override
public void onClick(DialogInterface dialog, int which) {

if (cityInput.getText().toString().equals("")) {
Toast.makeText(MainActivity.this, "Please type a City name!", Toast.LENGTH_LONG).show();
return;
}

CityPreference cityPreference = new CityPreference(MainActivity.this);
cityPreference.setCity(cityInput.getText().toString());

String newCity = cityPreference.getCity();
renderWeatherData(newCity);
}
});
builder.show();

}


This is the code for the renderWeatherData:

public void renderWeatherData(String city) {
WeatherTask weatherTask = new WeatherTask();
weatherTask.execute(new String[]{city + "&units=metric"});
}


The error upon crashing is:

07-25 19:03:28.507 24691-25279/? E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #2
Process: com.vladimirtumbev.android.footballscorekeeper, PID: 24691
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:309)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
at org.json.JSONTokener.nextCleanInternal(JSONTokener.java:116)
at org.json.JSONTokener.nextValue(JSONTokener.java:94)
at org.json.JSONObject.<init>(JSONObject.java:156)
at org.json.JSONObject.<init>(JSONObject.java:173)
at data.JSONWeatherParser.getWeather(JSONWeatherParser.java:24)
at com.vladimirtumbev.android.footballscorekeeper.MainActivity$WeatherTask.doInBackground(MainActivity.java:465)
at com.vladimirtumbev.android.footballscorekeeper.MainActivity$WeatherTask.doInBackground(MainActivity.java:458)
at android.os.AsyncTask$2.call(AsyncTask.java:295)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
at java.lang.Thread.run(Thread.java:818)


Both my classes that extend the AsyncTask:

private class DownloadImageAsyncTask extends AsyncTask<String, Void, Bitmap> {

@Override
protected Bitmap doInBackground(String... params) {
return downloadImage(params[0]);
}


@Override
protected void onPostExecute(Bitmap bitmap) {
iconView.setImageBitmap(bitmap);
}


private Bitmap downloadImage(String code) {
final DefaultHttpClient client = new DefaultHttpClient();

final HttpGet getRequest = new HttpGet(Utils.WEATHER_ICON_URL + code + ".png");

try {
HttpResponse response = client.execute(getRequest);

final int statusCode = response.getStatusLine().getStatusCode();

if (statusCode != HttpStatus.SC_OK) {

Log.e("DownloadImage", "Error:" + statusCode);
return null;
}
final HttpEntity entity = response.getEntity();

if (entity != null) {

InputStream inputStream = null;
inputStream = entity.getContent();

//decode contents from the stream

final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;

}

} catch (IOException e) {
e.printStackTrace();
}
return null;
}

}

private class WeatherTask extends AsyncTask<String, Void, Weather> {

@Override
protected Weather doInBackground(String... params) {

String data = ((new WeatherHTTPClient()).getWeatherData(params[0]));

weather = JSONWeatherParser.getWeather(data);
weather.iconData = weather.currentCondition.getIcon();

new DownloadImageAsyncTask().execute(weather.iconData);

return weather;
}

@Override
protected void onPostExecute(Weather weather) {
super.onPostExecute(weather);

DecimalFormat decimalFormat = new DecimalFormat("#.#");

String tempFormat = decimalFormat.format(weather.currentCondition.getTemperature());

cityName.setText(weather.place.getCity() + "," + weather.place.getCountry());
temp.setText("" + tempFormat + "°C");
description.setText(weather.currentCondition.getCondition() + "(" + weather.currentCondition.getDescription() + ")");

}

}


My HTTP helper class:

public class WeatherHTTPClient {

public String getWeatherData(String place){
HttpURLConnection connection = null;

InputStream inputStream = null;

try {
connection = (HttpURLConnection) (new URL(Utils.WEATHER_BASE_URL + place + Utils.WEATHER_API_KEY)).openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
// connection.setDoOutput(true);
connection.connect();
if(connection.getResponseCode() == 200) // 200 is ok
{

//Read the Response

StringBuffer stringBuffer = new StringBuffer();
inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;

while ((line = bufferedReader.readLine()) != null) {

stringBuffer.append(line + "\r\n");
}
inputStream.close();
connection.disconnect();
return stringBuffer.toString();
}

} catch (IOException e) {
e.printStackTrace();
}

return null;

}

}


My JSON Weather Parser class:

public class JSONWeatherParser {

public static Weather getWeather(String data){

Weather weather = new Weather();

//Create JSON object from data

try {
JSONObject jsonObject = new JSONObject(data);

Place place = new Place();


JSONObject coordObj = Utils.getObject("coord", jsonObject);
place.setLat(Utils.getFloat("lat", coordObj));
place.setLon(Utils.getFloat("lon", coordObj));


//get Sys object

JSONObject sysObj = Utils.getObject("sys", jsonObject);
place.setCountry(Utils.getString("country",sysObj));
place.setCity(Utils.getString("name",jsonObject));
weather.place = place;

//get Weather info

JSONArray jsonArray = jsonObject.getJSONArray("weather");
JSONObject jsonWeather = jsonArray.getJSONObject(0);
weather.currentCondition.setWeatherId(Utils.getInt("id", jsonWeather));
weather.currentCondition.setDescription(Utils.getString("description", jsonWeather));
weather.currentCondition.setCondition(Utils.getString("main", jsonWeather));
weather.currentCondition.setIcon(Utils.getString("icon", jsonWeather));

JSONObject mainObj = Utils.getObject("main", jsonObject);
weather.currentCondition.setTemperature(Utils.getDouble("temp", mainObj));

JSONObject cloudObj = Utils.getObject("clouds", jsonObject);
weather.clouds.setPrecipitation(Utils.getInt("all", cloudObj));

return weather;

} catch (JSONException e) {
e.printStackTrace();

return null;
}


I have some more Model classes with setters and getters and can add them if needed.

Thank you for the help!!!

Answer Source

Here is the line from your stack trace that is important

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference

You've got NullPointerException (NPE). You are trying to get a length of a String that is null, that causes it to crash. There is a line number in a stack trace that points to where this call occurs. So find that line and surround it with

try { 
 //line that throws exception
} catch (ExceptionType name) {
  //do something in a case of exception       
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download