I'm new to Android and using web APIs, and I'm writing an Android App that scans a barcode from a book and then search its ISBN in Google Books API.
I have this url after the barcode scan: https://www.googleapis.com/books/v1/volumes?q=isbn:9788432250651&AIzaSyCpYez5556X4UzPV6rF4kkspj9DsCs_Q_c
And the next code:
private class GetBookInfo extends AsyncTask <View, Void, Integer> {
#Override
protected Integer doInBackground(View... urls) {
// make Call to the url
makeCall("https://www.googleapis.com/books/v1/volumes?" +
"q=isbn:" + ean_content + "&AIzaSyCpYez5556X4UzPV6rF4kkspj9DsCs_Q_c");
//print the call in the console
System.out.println("https://www.googleapis.com/books/v1/volumes?" +
"q=isbn:" + ean_content + "&AIzaSyCpYez5556X4UzPV6rF4kkspj9DsCs_Q_c");
return null;
}
#Override
protected void onPreExecute() {
// we can start a progress bar here
}
#Override
protected void onPostExecute(Integer result) {
String ruta = save_cover(getApplicationContext(), title, book_cover);
Intent intent = new Intent(MainActivity.this, Spreadsheets.class);
// intent.putExtra(title,title);
// intent.putExtra(author,authors);
// intent.putExtra(date,date);
// intent.putExtra(category,categories);
// intent.putExtra(description,description);
//finish();
startActivity(intent);
finish();
}
}
public void makeCall(String stringURL) {
URL url = null;
BufferedInputStream is = null;
JsonReader jsonReader;
try {
url = new URL(stringURL);
} catch (Exception ex) {
System.out.println("Malformed URL");
}
try {
if (url != null) {
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
is = new BufferedInputStream(urlConnection.getInputStream());
}
} catch (IOException ioe) {
System.out.println("IOException");
}
if (is != null) {
try {
jsonReader = new JsonReader(new InputStreamReader(is, "UTF-8"));
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("title")) {
title = jsonReader.nextString();
}
else if (name.equals("authors")) {
authors = jsonReader.nextString();
}
else if (name.equals("publishedDate")) {
date = jsonReader.nextString();
}
else if (name.equals("categories")) {
categories = jsonReader.nextString();
}
else if (name.equals("description")) {
description = jsonReader.nextString();
}
// else if (name.equals("averageRating")) {
// rating = jsonReader.nextString();
// }
else if (name.equals("thumbnail")) {
image = jsonReader.nextString();
book_cover = download_cover(image);
}
else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
}
catch (Exception e) {
System.out.println("Exception");
}
}
}
This isn't retrieving anything from the API. I would appreciate your help, thank you!
I think what you need to do next is request a connection from the API, open the connection, using JSON retrieve data from the API and use the inputStream to get the data stored in an array.
something like :Implement these methods in a class:
private static String makeHttpRequest(URL url) throws IOException
private static String readFromStream(InputStream inputStream) throws IOException
private static List extractFeatureFromJson(String booksJson)
public static List featchBookData(String requestUrl)
Here is a full code example of how to use Google Books API in Android with Feign or Retrofit. These libraries provide a higher level abstraction on top of HTTP so that you can use simple method calls and objects in your code, instead of messing with requests, responses and JSON deserialization.
Related
I got an api token for my api but don't know how to implement it in my code...
this is the code i have for reaching my api:
public class DataVoetbalWebservice extends AsyncTask<VoetbalDataInterface, Void, JSONArray> {
private static final int CONNECTION_TIMEOUT = 12000;
private static final int DATARETRIEVAL_TIMEOUT = 12000;
VoetbalDataInterface listener;
private ProgressDialog dialog;
Context context;
public void setActivity(Context context) {
this.context = context;
dialog = new ProgressDialog(context);
}
#Override
protected void onPreExecute() {
dialog.setMessage("De Verschillende competities ophalen, even geduld aub.");
dialog.show();
}
#Override
protected JSONArray doInBackground(VoetbalDataInterface... params) {
listener = params[0];
// execute search
disableConnectionReuseIfNecessary();
HttpURLConnection urlConnection = null;
try {
// create connection
//URL urlToRequest = new URL("http://datatank.stad.gent/4/bevolking/geboortes.json?%2Fbevolking%2Fgeboortes=");
URL urlToRequest = new URL("http://api.football-data.org/v1/competitions");
urlConnection = (HttpURLConnection)
urlToRequest.openConnection();
urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);
urlConnection.setReadTimeout(DATARETRIEVAL_TIMEOUT);
// handle issues
int statusCode = urlConnection.getResponseCode();
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
} else if (statusCode != HttpURLConnection.HTTP_OK) {
// handle any other errors, like 404, 500,..
}
// create JSON object from content
InputStream in = new BufferedInputStream(
urlConnection.getInputStream());
return new JSONArray(getResponseText(in));
} catch (MalformedURLException e) {
// URL is invalid
Log.d("Info", e.getMessage());
} catch (SocketTimeoutException e) {
// data retrieval or connection timed out
Log.d("Info", e.getMessage());
} catch (IOException e) {
// could not read response body
// (could not create input stream)
Log.d("Info", e.getMessage());
} catch (JSONException e) {
// response body is no valid JSON string
Log.d("Info", e.getMessage());
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
#Override
protected void onPostExecute(JSONArray json) {
if (dialog.isShowing()) {
dialog.dismiss();
}
ArrayList <Competitie> competities = new ArrayList<>();
try {
for (int i = 0; i < json.length(); i++) {
Competitie competitie = new Competitie();
JSONObject jsonObject = json.getJSONObject(i);
competitie.setId(jsonObject.getInt("id"));
competitie.setCaption(jsonObject.getString("caption"));
competitie.setLeague(jsonObject.getString("league"));
competitie.setYear(jsonObject.getString("year"));
competitie.setCurrentMatchday(jsonObject.getInt("currentMatchday"));
competitie.setNumberOfMatchdays(jsonObject.getInt("numberOfMatchdays"));
competitie.setNumberOfTeams(jsonObject.getInt("numberOfTeams"));
competitie.setNumberOfGames(jsonObject.getInt("numberOfGames"));
JSONObject links = jsonObject.getJSONObject("_links");
JSONObject teams = links.getJSONObject("teams");
JSONObject stand = links.getJSONObject("leagueTable");
competitie.setTeamString(teams.getString("href"));
competitie.setStandUrl(stand.getString("href"));
competities.add(competitie);
}
} catch (JSONException e) {
e.printStackTrace();
}
listener.updateScreenCompetities(competities);
}
/**
* required in order to prevent issues in earlier Android version.
*/
private static void disableConnectionReuseIfNecessary() {
// see HttpURLConnection API doc
if (Integer.parseInt(Build.VERSION.SDK)
< Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
private static String getResponseText(InputStream inStream) {
// very nice trick from
// http://weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html
return new Scanner(inStream).useDelimiter("\\A").next();
}
}
where do i add the token so i can contact the api?..
because for know i can't read anymore data because the request capacity is full. i already got my api token emailed to me.
This is the documentation for the api, how do i add the request header?
Documentation about token use
thanks in advance
EDIT
added this line of code and now it works!
urlConnection.setRequestProperty("X-Auth-Token","6a0c52afadac44f5bc65bd0dcfb363c2");
I am storing the data that I parsed from the JSON that is returned by my API request into the Firebase database.
submitButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String APIURL = "https://api.github.com/users/" + idInput.getText().toString();
String repoURL = "https://api.github.com/users/" + idInput.getText().toString() + "/repos";
new JSONTask().execute(APIURL);
//new JSONTask().execute(repoURL);
String parsedUserID = idInput.getText().toString();
SM.sendDataToProfile(parsedUserID);
viewPager.setCurrentItem(1);
//addUser(parsedUserID);
}
});
When the button is clicked, it calls a new JSONTask (asynctask) on the APIURL.
JSONTask
public class JSONTask extends AsyncTask<String, String, String> {
#Override
// Any non-UI thread process is running in this method. After completion, it sends the result to OnPostExecute
protected String doInBackground(String... params) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
// Pass in a String and convert to URL
URL url = new URL(params[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream stream = connection.getInputStream();
// Reads the data line by line
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer strBuffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
strBuffer.append(line);
}
// If we are able to get the data do below :
String retreivedJson = strBuffer.toString();
return retreivedJson;
// When we are not able to retreive the Data
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
// close both connection and the reader
connection.disconnect();
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
And it does parsing in another function.
My question is, as you can see on my setOnClickListener, I tried to make two JSONTask on two different URLs because the first URL gives me the information of the user and the second URL (repoURL) gives me the information of the user's repositories. I tried to fetch the repo info of the user and store it into the DB, but it seems like this is a wrong approach.
What is a right way to call two separate AsyncTasks on two different URLs?
EDIT
private void addUserRepo(final String githubID, final String[] repoList) {
DatabaseReference users = databaseReference.child("users");
users.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List list = new ArrayList<String>(Arrays.asList(repoList));
databaseReference.child("users").child(githubID).child("Repos").setValue(list);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Using data parsed from
public void formatJSONArray(String results){
try {
JSONArray jsonArray = new JSONArray(results);
RepoInfo[] repoList = new RepoInfo[jsonArray.length()];
for(int i = 0; i < jsonArray.length(); i++){
JSONObject jsonObject=jsonArray.getJSONObject(i);
if(jsonObject.optString("name") != null) {
repoList[i].setRepoName(jsonObject.getString("name"));
//repoNameList.add(jsonObject.getString("name"));
}
if(jsonObject.optString("description") != null) {
repoList[i].setDescription(jsonObject.getString("description"));
//descriptionList.add(jsonObject.getString("description"));
}
if(jsonObject.optJSONObject("owner") != null){
JSONObject ownerObject=jsonObject.getJSONObject("owner");
if(ownerObject.optString("login")!=null) {
repoList[i].setOwner(ownerObject.getString("login"));
//userNameList.add(ownerObject.getString("login"));
}
}
}
} catch (JSONException jsonException){
}
}
The response of two different URLs will surely not be similar. So you need different parse methods for them.
One lazy way would be to use two different AsyncTasks subclasses for two different urls.
Another way would be to store a flag inside the asynctask indicating whether it is dealing with user or repo.
public class JSONTask extends AsyncTask <String , String , String> {
boolean fetchingRepo;
#Override
protected String doInBackground (String... params) {
fetchingRepo = params[0].endsWith("/repos");
//other statements
}
Now inside onPostExecute:
if(fetchingRepo){
//parse one way
} else {
//parse another way
}
So here in Java I've written a typical class, to send json to a rest server. (I'll include the whole class below for clarity.) So that's a file "Fetcher.java"
Now for the callback you need an interface. The interface is trivial, just one function with a string.
public interface FetcherInterface {
public void fetcherDone(String result);
}
Annoyingly you need a whole file for that, "FetcherInterface.java"
So this interface is nothing but "one callback with a string". Often all you need is just "one callback with no arguments".
In fact ........ are there some sort of standard interfaces I can use, or something like that?
It seems kind of annoying to have to put in a whole interface for such a simple "standard" interface.
What's the deal on this? What's the javaly solution?
It seems you CAN NOT put it in the same file:
Perhaps I misunderstand something there. If you could put it in the same file, that would be convenient at least.
(Lambdas are not yet practically available. Anyway, sometimes you want an interface.)
Just for clarity, here's how you call the class
JSONObject j = new JSONObject();
try {
j.put("height", 2.1);
j.put("width", 2.5);
j.put("command", "blah");
} catch (JSONException e) {
e.printStackTrace();
}
new Fetcher("mobile/login", j, new FetcherInterface() {
#Override
public void fetcherDone(String result) {
Log.d("DEV","all done");
doSomething(result);
}
}
).execute();
or indeed
public class HappyClass extends Activity implements FetcherInterface {
...
private void someCall() {
JSONObject j = new JSONObject();
try {
j.put("height", 2.1);
j.put("width", 2.5);
j.put("command", "blah");
} catch (JSONException e) {
e.printStackTrace();
}
new Fetcher("mobile/data", j, this).execute();
devBlank();
}
#Override
public void fetcherDone(String result) {
Log.d("DEV","all done" +result);
doSomething(result);
}
Here's the whole class... Fetcher.java file
public class Fetcher extends AsyncTask<Void, Void, String> {
private String urlTail;
private JSONObject jsonToSend;
private FetcherInterface callback;
// initializer...
Fetcher(String ut, JSONObject toSend, FetcherInterface cb) {
urlTail = ut;
jsonToSend = toSend;
callback = cb;
}
#Override
protected String doInBackground(Void... params) {
HttpURLConnection urlConnection = null; // declare outside try, to close in finally
BufferedReader reader = null; // declare outside try, to close in finally
String rawJsonResultString = null;
String json = jsonToSend.toString();
Log.d("DEV","the json string in Fetcher is " +json);
try {
URL url = new URL("https://falcon.totalfsm.com/" + urlTail);
Log.d("DEV","the full URL in Fetcher is " +url);
// open a json-in-the-body type of connection.......
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setConnectTimeout(5000);
// urlConnection.setDoOutput(false); // can be important?
urlConnection.connect();
OutputStream os = urlConnection.getOutputStream();
os.write(json.getBytes("UTF-8"));
os.close();
// annoyingly, you have to choose normal versus error stream...
InputStream inputStream;
int status = urlConnection.getResponseCode();
if (status != HttpURLConnection.HTTP_OK)
inputStream = urlConnection.getErrorStream();
else
inputStream = urlConnection.getInputStream();
if (inputStream == null) { // nothing to do.
return null;
}
StringBuffer buffer = new StringBuffer();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) { // adding newlines makes debugging easier
buffer.append(line + "\n");
}
if (buffer.length() == 0) { // stream was empty
return null;
}
rawJsonResultString = buffer.toString();
return rawJsonResultString;
} catch (IOException e) {
Log.e("PlaceholderFragment", "Error ", e);
return null;
} finally{
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
Log.e("PlaceholderFragment", "Error closing stream", e);
}
}
}
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.d("DEV", "Fetcher done");
if (s==null) {
Log.d("DEV","applying anti-null measures in Fetcher!");
s = "message from app communications layer: 'null' returned from servers for that call at " +urlTail;
}
callback.fetcherDone(s);
}
}
I feel bad answering my own question, but as there were no other answers this info may be helpful.
DaveNewton and Rowtang have supplied the exact answers here:
(Point 1) If you want a genuinely public interface, it goes in its own file. That's how Java works. There's no alternative.
(Point 2) Normally, use protected interface and declare the interface inside the class. It can then be used throughout the app.
So...
public class Fetcher extends AsyncTask<Void, Void, String> {
protected interface FetcherInterface {
public void fetcherDone(String result);
}
private String urlTail;
private JSONObject jsonToSend;
private FetcherInterface callback;
Fetcher(String ut, JSONObject toSend, FetcherInterface cb) {
urlTail = ut;
jsonToSend = toSend;
callback = cb;
}
#Override
protected String doInBackground(Void... params) {
....
(c# programmers would maybe call it "IFetcher".)
I have an option menu item that allows a user to see their current location (based on Zip Code) on Google Maps using an intent. Because Google Maps only accepts Lat/Lng, I am using the Geocoding API to return Lat/Lng in JSON format. Here is the code that executes once the user selects the menu item:
MainActivity.java
#Override public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
Intent in = new Intent(this, SettingsActivity.class);
startActivity(in);
return true;
}
if (id == R.id.action_map) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String location = prefs.getString(getString(R.string.pref_location_key),
getString(R.string.pref_location_default));
FetchZipTask fzt = new FetchZipTask();
fzt.execute(location);
loc = fzt.locale;
Uri geoLocation = Uri.parse("geo:"+ loc);
Log.d("Debug", geoLocation.toString());
Intent in = new Intent(Intent.ACTION_VIEW);
in.setData(geoLocation);
if (in.resolveActivity(getPackageManager()) != null) {
startActivity(in);
}
}
return super.onOptionsItemSelected(item);
}
I am currently trying to use a public String field in the AsyncTask class that is updated when the onPostExecute() method parses the JSON and formats the retrieved Lat/Lng string. I then access this public field from the MainActivity class whenever the user selects the menu item, but the field is always null. What am I doing wrong, and is it the most effective way to leverage AsyncTask? I'm thinking there must be a better way to return the value.
FetchZipTask.java
public class FetchZipTask extends AsyncTask<String, Void, String> {
public String locale = null;
#Override protected void onPostExecute(String result) {
locale = result;
}
#Override protected String doInBackground(String... params) {
if (params.length == 0) {
return null;
}
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
//raw JSON response as a string
String locationJsonStr = null;
try {
final String BASE_LOCATION_URL =
"https://maps.googleapis.com/maps/api/geocode/json?";
final String ADDRESS_PARAM = "address";
final String APPID_PARAM = "key";
// URI.path vs URI.parse vs. URI Scheme
Uri builtUri = Uri.parse(BASE_LOCATION_URL)
.buildUpon()
.appendQueryParameter(ADDRESS_PARAM, params[0])
.appendQueryParameter(APPID_PARAM, BuildConfig.GOOGLE_GEOCODE_API_KEY)
.build();
//Log.d("Debug", builtUri.toString());
URL url = new URL(builtUri.toString());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null) {
// Nothing to do.
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// buffer for debugging.
line.concat(" Hello ");
line.concat("\n");
buffer.append(line);
}
if (buffer.length() == 0) {
// Stream was empty. No point in parsing.
return null;
}
locationJsonStr = buffer.toString();
Log.v("debug", "Location string: " + locationJsonStr);
} catch (IOException e) {
return null;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
Log.e("ForecastFragment", "Error closing stream", e);
}
}
}
try {
return getLocationDataFromJson(locationJsonStr);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
private String getLocationDataFromJson(String forecastJsonStr) throws
JSONException {
// These are the names of the JSON objects that need to be extracted.
final String GEO_LIST = "results";
final String GEO_OBJ = "geometry";
final String GEO_LOC = "location";
final String GEO_LAT = "lat";
final String GEO_LNG = "lng";
JSONObject forecastJson = new JSONObject(forecastJsonStr);
JSONArray resultsArray = forecastJson.getJSONArray(GEO_LIST);
JSONObject resultsObj = resultsArray.getJSONObject(0);
JSONObject geoObj = resultsObj.getJSONObject(GEO_OBJ);
JSONObject latLng = geoObj.getJSONObject(GEO_LOC);
String lat = latLng.getString(GEO_LAT);
String lng = latLng.getString(GEO_LNG);
Log.d("location", "Lat:" + lat + "\n Lng:" + lng);
return lat + "," + lng;
}
}
AsyncTask is called async for a reason.
In the following code you execute your AsyncTask and then immediately try to access one of its fields:
FetchZipTask fzt = new FetchZipTask();
fzt.execute(location);
loc = fzt.locale;
That won't work because FetchZipTask may still be running when you're trying to access its locale variable.
onPostExecute() is called when the task is finished, so you should pass your result from there.
You could define an interface in FetchZipTask, pass an instance of it as a constructor param and call the appropriate method on that instance in onPostExecute():
public class FetchZipTask extends AsyncTask<String, Void, String> {
// declaring a listener instance
private OnFetchFinishedListener listener;
// the listener interface
public interface OnFetchFinishedListener {
void onFetchFinished(String result);
}
// getting a listener instance from the constructor
public FetchZipTask(OnFetchFinishedListener listener) {
this.listener = listener;
}
// ...
// calling a method of the listener with the result
#Override protected void onPostExecute(String result) {
listener.onFetchFinished(result);
}
}
In your Activity, pass an OnFetchFinishedListener when instantiating your AsyncTask:
new FetchZipTask(new FetchZipTask.OnFetchFinishedListener() {
#Override
public void onFetchFinished(String result) {
// do whatever you want with the result
Uri geoLocation = Uri.parse("geo:"+ result);
Log.d("Debug", geoLocation.toString());
Intent in = new Intent(Intent.ACTION_VIEW);
in.setData(geoLocation);
if (in.resolveActivity(getPackageManager()) != null) {
startActivity(in);
}
}
}).execute();
And that's it. Orientation change may still be a problem, so you could move your AsyncTask in a headless Fragment, or consider using a Service instead.
This the most bizarre problem I have ever seen. I get "No product available" although there are products in my database.
Here my service:
public class AllProductsService {
private String URL = "xxxx";
Gson gson;
public AllProductsService(int page) {
gson = new Gson();
URL = URL + "?page=" + Integer.toString(page);
}
private InputStream sendRequest(URL url) throws Exception {
try {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return urlConnection.getInputStream();
}
} catch (Exception e) {
throw new Exception("");
}
return null;
}
public List<Product> getProducts() {
try {
InputStream inputStream = sendRequest(new URL(URL));
if(inputStream != null) {
InputStreamReader reader = new InputStreamReader(inputStream);
return gson.fromJson(reader, new TypeToken<List<Product>>(){}.getType());
}
}
catch (Exception e) {
}
return null;
}
}
And my AsyncTask class:
private class AllProductsTask extends AsyncTask<Void, Void, List<Product>> {
#Override
protected void onPreExecute() {
setSupportProgressBarIndeterminateVisibility(true);
}
#Override
protected List<Product> doInBackground(Void... params) {
AllProductsService allProductsService = new AllProductsService(current_page);
List<Product> liste = allProductsService.getProducts();
if (liste != null && liste.size() >= 1)
return liste;
return new ArrayList<Product>();
}
protected void onPostExecute(java.util.List<Product> result) {
setSupportProgressBarIndeterminateVisibility(false);
if (result.isEmpty() && isInternetPresent && current_page < 2) {
Crouton.makeText(MainActivity.this, "No product available!", Style.ALERT).show();
}
//populate adapter
}
}
When I call the URL from the browser, results are displayed correctly. I also try with a different URL with the same code and it works fine. I don't know why.
I think problem is; you are returning the
new ArrayList<Product>();
in doInBackground() of Asynctask which is null. You should return the liste here. or place the return new ArrayList<Product>(); in else condition
I found the solution: just have to remove the slash at the end of the URL. Thank you #trevor-e. Knowing the HTTP code status help me.