0

I want to use the open weather api to display the weather in an app. I think Android Studio is not able to connect to the url. This is my code:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("JSON", "onCreate");
        DownloadWeatherData asyncTask = new DownloadWeatherData();
        asyncTask.execute("http://api.openweathermap.org/data/2.5/forecast?id=524901&appid=[APIkey]");
    }
    private class DownloadWeatherData extends AsyncTask<String, Void, Void>{

        @Override
        protected Void doInBackground(String... strings) {
            Log.d("JSON", "doInBackground");

            try{
                Log.d("JSON", strings[0]);

                URL REST = new URL(strings[0]);
                URLConnection connect = REST.openConnection();
                Log.d("JSON", connect.toString());
                InputStream stream = connect.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
                StringBuilder builder = new StringBuilder();
                String line;
                while((line = reader.readLine()) != null) {
                    builder.append(line);
                }
                String jsonString = builder.toString();
                JSONObject json = new JSONObject(jsonString);

                Log.d("JSON", jsonString);

            }catch(IOException e){
                e.printStackTrace();
                Log.d("JSON", "IO EXCEPTION");

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

The logcat keeps going into the IO EXCEPTION from the catch block.

I tried adding the following segment of code in the AndriodManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

This did not work. How can I fix this issue?

  • See this answer about using `http` and setting `usesCleartextTraffic`: https://stackoverflow.com/a/66263301/17856705. – Computable Jan 17 '23 at 02:00

1 Answers1

0

There are several issues here.

1st: You are doing a HTTP request instead of a HTTPS

By default, HTTP request is blocked and to enable it, you can try to add android:usesCleartextTraffic in your Android manifest to resolve it.

In the comment, someone have also mentioned that.

...
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true">
...

2nd: Check status code before processing the Connection#InputStream

If an error response (e.g., 401, 403, 404, 500) is returned from your HTTP request, you can't get the input stream and it will throw an IOException when you do so.

You shall cast the URLConnection object to HttpURLConnection and read the status code first before processing the input stream.

Example based on your code

private class DownloadWeatherData extends AsyncTask<String, Void, Void> {
        // RECOMMENDATION: Use class name as TAG and use it as Logcat TAG
        private static final String TAG = "DownloadWeatherData";

        @Override
        protected Void doInBackground(String... strings) {
            // RECOMMENDATION: In msg (2nd arg), we shall add the method name as prefix
            Log.d(TAG, "doInBackground: called");

            try{
                Log.d(TAG, "doInBackground: [ args=" + strings[0] + " ]");

                URL REST = new URL(strings[0]);

                // CHANGE: Use HttpURLConnection instead of URLConnection as we want to check the response status code first
                // RECOMMENDATION: Variable name from connect to conn since its a connection object and not an action
                HttpURLConnection conn = (HttpURLConnection) REST.openConnection();
                conn.connect();


                Log.d(TAG, "doInBackground: " + conn.toString());
                int resCode = conn.getResponseCode();
                Log.d(TAG, "resCode=" + resCode);

                // ADD: Check response code before getting the RES as input stream
                if (resCode >= 200 && resCode <= 299) {
                    Log.d(TAG, "doInBackground: [ resCode=" + resCode + " ]");
                    // CHANGE: Only get the input stream WHEN the status code is expected
                    InputStream stream = conn.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
                    StringBuilder builder = new StringBuilder();
                    String line;
                    while((line = reader.readLine()) != null) {
                        builder.append(line);
                    }

                    String jsonString = builder.toString();
                    Log.d(TAG, "doInBackground:" + jsonString);

                    // RECOMMENDATION: Handle JSON parse fail
                    JSONObject json = new JSONObject(jsonString);
                } else {
                    throw new RuntimeException("Status code is not 200 to 299");
                }
            }catch(IOException e){
                // RECOMMENDATION print exception using Log.e
                Log.e(TAG, "doInBackground: IOException occurs.", e);
            } catch (JSONException e) {
                Log.e(TAG, "doInBackground: JSONException occurs.", e);
            } catch (RuntimeException e) {
                Log.e(TAG, "doInBackground: RuntimeException occurs.", e);
            }

            return null;
        }
    }

Recommendation 1

I know that if you are just trying to play with a REST URL in native Android, you will want to use native URLConnection or HttpURLConnection.

But if you decide to keep on going, you shall use the following libraries to assist you.

Recommendation 2

Do not use AsyncTask anymore. It has been deprecated in Android 11.

There are more convenient way to work on another thread to process your HTTP request.

In modern development, the most common way I used is Executors in Java and Coroutine in Kotlin. Both can control:

  • Which thread do you want the task/action to be executed in

  • Whether the task shall be executed in an async or a sync way