0

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!!!

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
  • Although I can see why it was marked as duplicate, due to lack of my general knowledge I had to ask the question specifically for my case just because I was unable to achieve success with the analogies in the other questions. None of the 3 answers completely answered my question, but I will be giving the "correct answer" to Al Pimenov as it pointed me in the right direct with the comment under his answer and I managed to fix the issue! – Vladimir Tumbev Jul 26 '17 at 15:21

3 Answers3

0

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       
}
A_P
  • 331
  • 3
  • 15
  • Where would i add this ? could you give me a bit more info, please? – Vladimir Tumbev Jul 25 '17 at 20:53
  • I edited the answer, 404 is not what causing your app to crash. If you are using any IDE just click on the line number in a stack trace and it will bring you to the place of the error – A_P Jul 26 '17 at 13:57
  • I didn't have to wrap it with a try and catch, I just had to "if else" in main activity the asynctasks but your most recent comment helped a lot in pointing me in the right direction, thus I am giving you the upvote and the correct answer! :) – Vladimir Tumbev Jul 26 '17 at 15:22
0

In the WeatherHTTPClient class, similar to

if(connection.getResponseCode() == 200) // 200 is ok
            {

add a condition for ResponseCode 404 and handle it accordingly.

Vamsi
  • 878
  • 1
  • 8
  • 25
0

You can't parse nothing, so you must handle that case explicitly

public class JSONWeatherParser {

    public static Weather getWeather(String data){
        if (data == null) return null; // return back null 

        Weather weather = new Weather();

It's up to you to popup some messages in the UI in the condition that you weren't able to parse a response

HttpGet and HTTPClient are deprecated, by the way, so I'd suggest you look into at least Volley or Retrofit+Gson

Also Glide or Picasso for loading images

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245