1

I previously developed a real time currency converter program, and would like to export it as an Android app. To do so, I am using a GET request with RestTemplate to get the rate between 2 currencies. However, I then get this error: android.os.NetworkOnMainThreadException

Here is the code I first tried:

   public void displayResult(View view) {

    TextView output = (TextView) findViewById(R.id.conversion);
    output.setInputType(InputType.TYPE_CLASS_NUMBER);
    output.setText(String.valueOf(convert()));
}

float convert() {

    EditText textBaseCurrency = (EditText) findViewById(R.id.BASE_CURRENCY);
    EditText textTargetCurrency = (EditText) findViewById(R.id.TARGET_CURRENCY);
    EditText textAmount = (EditText) findViewById(R.id.AMOUNT);

    String baseCurrency = textBaseCurrency.getText().toString().toUpperCase();
    String targetCurrency = textTargetCurrency.getText().toString().toUpperCase();
    float amount = Integer.parseInt(textAmount.getText().toString());

    restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    String url = "https://v6.exchangerate-api.com/v6/" + API_KEY + "/latest/" + baseCurrency;
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
    System.out.println(responseEntity);

    ObjectMapper mapper = new ObjectMapper();
    JsonNode root = null;
    try {
        root = mapper.readTree(responseEntity.getBody());
    } catch (IOException e) {
        e.printStackTrace();
    }
    JsonNode name = root.get(conversionRates);
    JsonNode rates = name.get(targetCurrency);
    float rate = rates.floatValue();
    float newAmount = rate * amount;

    return newAmount;
}

I saw that the solution would be to use AsyncTask. I tried it, but I wasn't successful:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    new Connection().execute();
}


    public class Connection extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... voids) {

        EditText textBaseCurrency = (EditText) findViewById(R.id.BASE_CURRENCY);
        EditText textTargetCurrency = (EditText) findViewById(R.id.TARGET_CURRENCY);
        EditText textAmount = (EditText) findViewById(R.id.AMOUNT);

        String baseCurrency = textBaseCurrency.getText().toString().toUpperCase();
        String targetCurrency = textTargetCurrency.getText().toString().toUpperCase();
        float amount = Integer.parseInt(textAmount.getText().toString());

        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
        String url = "https://v6.exchangerate-api.com/v6/" + API_KEY + "/latest/" + baseCurrency;
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
        System.out.println(responseEntity);

        ObjectMapper mapper = new ObjectMapper();
        JsonNode root = null;
        try {
            root = mapper.readTree(responseEntity.getBody());
        } catch (IOException e) {
            e.printStackTrace();
        }
        JsonNode name = root.get(conversionRates);
        JsonNode rates = name.get(targetCurrency);
        float rate = rates.floatValue();
        float newAmount = rate * amount;

        return null;
    }

Moreover, this is now deprecated.

My gradle.build file:

    packagingOptions{
    exclude 'META-INF/notice.txt'
    exclude 'META-INF/license.txt'
}dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.10.6'
implementation 'javax.xml.stream:stax-api:1.0'
implementation 'org.springframework.android:spring-android-rest-template:1.0.1.RELEASE'

}

I also added <uses-permission android:name="android.permission.INTERNET" /> in my AndroidManifest.xml

LaChope
  • 183
  • 1
  • 2
  • 14
  • `I saw that the solution would be to use AsyncTask. I tried it, but I wasn't successful.` - show what you have tried, this is proper approach. You ma exchange `AsyncTask` for some other solution, e.g. like [HERE](https://stackoverflow.com/questions/58767733/android-asynctask-api-deprecating-in-android-11-what-are-the-alternatives) – snachmsm Sep 07 '20 at 13:17

4 Answers4

1

You need to do your api process im background thread instead of Main thread

Use Async Task Use Retrofit Use Runnable

Choose one from these. There are many more

Vikas
  • 432
  • 4
  • 17
1

You can use some library like to do this:

but first of all you should add this permission:

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

but the easiest way is :

class RequestTask extends AsyncTask<String, String, String>{

    @Override
    protected String doInBackground(String... uri) {
        HttpClient httpclient = new DefaultHttpClient();
        HttpResponse response;
        String responseString = null;
        try {
            response = httpclient.execute(new HttpGet(uri[0]));
            StatusLine statusLine = response.getStatusLine();
            if(statusLine.getStatusCode() == HttpStatus.SC_OK){
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                response.getEntity().writeTo(out);
                responseString = out.toString();
                out.close();
            } else{
                //Closes the connection.
                response.getEntity().getContent().close();
                throw new IOException(statusLine.getReasonPhrase());
            }
        } catch (ClientProtocolException e) {
            //TODO Handle problems..
        } catch (IOException e) {
            //TODO Handle problems..
        }
        return responseString;
    }
    
    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        //Do anything with response..
    }
}

You then can make a request by:

   new RequestTask().execute("https://v6.exchangerate-api.com/v6/" + API_KEY + "/latest/" + baseCurrency);

keep in mind if you use this application on android 9 you should use this attribute to your AndroidManifest.xml where you allow all http for all requests:

<application android:usesCleartextTraffic="true">
</application>

But in case you want some more configurations for different links, for instance, allowing HTTP for some domains but not other domains you must provide res/xml/networkSecurityConfig.xml file.

To do this in Android 9 Pie you will have to set a networkSecurityConfig in your Manifest application tag like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config">




    </application>
</manifest>

Then in your xml folder you now have to create a file named network_security_config just like the way you have named it in the Manifest and from there the content of your file should be like this to enable all requests without encryptions:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

but note this:

HttpClient is not supported any more in sdk 23. You have to use URLConnection or downgrade to sdk 22 (compile 'com.android.support:appcompat-v7:22.2.0')

If you need sdk 23, add this to your gradle:

android {
    useLibrary 'org.apache.http.legacy'
}

You also may try to download and include HttpClient.jar directly into your project or use OkHttp instead

1

You cannot execute HTTP requests in the main thread as it . You need to run them on a different thread and then run a callback in the main thread.
Yes, you could use an AsyncTask but the code would be really verbose and I personally don't like AsyncTasks

However I'll suggest you to use more advanced libraries that work great like Retrofit or OkHttp

Francesco Re
  • 836
  • 6
  • 22
1

You have to perform Network related operations on a Separate thread... You can use AsyncTask

private class DownloadFilesTask extends AsyncTask<Void, Void, Float> {
    protected Float doInBackground(Void) {
         // put the convert function definition
    }

    protected void onPostExecute(Float result) {
        // update the UI after background processes completes
    }
}

Define this class as a subclass of that activity, then call it like

new MyTask().execute();
Sanjay S
  • 59
  • 1
  • 5