22

I'm working on a App that should get a JSON response from a webservice and write every element in a listview, I have read that I should work with AsyncTask to get the HTTP Response and I did it and I could retrieve data from the webservice and display them in TextViews. But when I try to display elements in a listview it doesn't display anything and gives me the following message in the logcat : 06-05 19:44:27.418: I/Choreographer(20731): Skipped 60 frames! The application may be doing too much work on its main thread.

here's my main code :

public class MainActivity extends Activity {

    private static JsonObject response = new JsonObject();
    private ArrayList<SearchResults> results = new ArrayList<SearchResults>(); 
    private SearchResults sr1 = null;

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

        new LoginAction().execute("");  

        ArrayList<SearchResults> searchResults = results;
        final ListView lv1 = (ListView) findViewById(R.id.ListView01);
        lv1.setAdapter(new MyCustomBaseAdapter(this, searchResults));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    private class LoginAction extends AsyncTask<String, Void, String> {

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

            Map<String, String> callArgs = new HashMap<String, String>(1);

            callArgs.put("suuid", "dtr0bdQGcqwSh3QO7fVwgVfBNWog6mvEbAyljlLX9E642Yfmur");

            try {
                response = EventPulseCloud.call("ListEvents", callArgs);
            } catch (HttpClientException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (JsonException e) {
                e.printStackTrace();
            } 

            return response.get("Type").toString();
        }

        protected void onPostExecute(String result) {

            if(result.equals("success")) {
                JsonArray records = null;
                try {
                    records = response.getObject ("Data").getArray ("Records");
                } catch (JsonException e) {
                    e.printStackTrace();
                }

                for(int i = 0; i < records.count(); i++) {
                    JsonObject record = (JsonObject) records.get(i);
                    sr1 = new SearchResults();
                    sr1.setAddress(record.get("address").toString());
                    results.add(sr1);
                }
            }   
        }   
    }
    }

My list adapter :

public class MyCustomBaseAdapter extends BaseAdapter {
    private static ArrayList<SearchResults> searchArrayList;

    private LayoutInflater mInflater;

    public MyCustomBaseAdapter(Context context, ArrayList<SearchResults> results) {
        searchArrayList = results;
        mInflater = LayoutInflater.from(context);
    }

    public int getCount() {
        return searchArrayList.size();
    }

    public Object getItem(int position) {
        return searchArrayList.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.custom_row_view, null);
            holder = new ViewHolder();
            holder.txtAddress = (TextView) convertView.findViewById(R.id.address);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.txtAddress.setText(searchArrayList.get(position).getAddress());

        return convertView;
    }

    static class ViewHolder {
        TextView txtAddress;
    }
}

and finally, SearchResults.java :

public class SearchResults {
    private String address = "";

    public void setAddress(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }
}

So, what do I do wrong ? Do you have an idea about this ?

Thank you.

  • 1
    This is not a solution but a test that might help out figure where the issue is coming from. That for loop could be the issue, try using a number like 1 instead of `records.count()` and see if you still get the error, just try trouble shooting each area that can be the issue. – Osman Jun 05 '13 at 20:20
  • let me know what happens...or you can output the length of records, if its too long try doing the processing on the back thread before you bring it to the main thread... – Osman Jun 05 '13 at 20:21
  • 1
    Thank you. it doesn't display anything but gives me `06-05 21:27:32.367: I/dalvikvm-heap(23426): Grow heap (frag case) to 8.924MB for 691216-byte allocation ` –  Jun 05 '13 at 20:29
  • thats good that means the issue is with the loop, try the answer below – Osman Jun 05 '13 at 20:49
  • 2
    ok so I realize that the issue is your setting your adapter in that loop so you cant move it to the background thread, give me a minute to put some code together... – Osman Jun 05 '13 at 20:52

2 Answers2

20

onPostExecute() happens on the Main UI thread. It looks like you are still doing a fair amount of work in that method that should be done off the UI thread, i.e. processing the response, iterating over JSON objects, etc. Do that in doInBackground() and have that return a list of results, so the only thing onPostExecute needs to do is pass the new items to your list adapter.

Also, do not use the same ArrayList as the one your adapter holds. If for some reason the adapter discovers that the data has changed without you having called notifyDataSetChanged(), it will probably crash (or at least display odd behaviors). Create a new ArrayList in your AsyncTask, then put this in your Adapter and call it from onPostExecute:

public void setListItems(ArrayList<SearchResult> newList) {
    searchArrayList = newList;
    notifyDataSetChanged();
}
Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Thank you, it was very interesting and helpful, But shouldn't the return of doInBackground only be a string ? –  Jun 06 '13 at 00:56
  • 1
    the return can be whatever you want, I think you should look more into how methods work in java and more specifically the async class you are using here... let me find some resources... – Osman Jun 06 '13 at 01:22
  • 1
    See here http://mobileorchard.com/android-app-developmentthreading-part-2-async-tasks/ http://androidresearch.wordpress.com/2012/03/17/understanding-asynctask-once-and-forever/ http://developer.android.com/reference/android/os/AsyncTask.html – Osman Jun 06 '13 at 01:31
9
private class LoginAction extends AsyncTaskList<String, Void, ArrayList<SearchResult>> {

    @Override
    protected ArrayList<SearchResult> doInBackground(String... params) {
        List<SearchResults> resultList =  new ArrayList<SearchResults>();

        Map<String, String> callArgs = new HashMap<String, String>(1);

        callArgs.put("suuid", "dtr0bdQGcqwSh3QO7fVwgVfBNWog6mvEbAyljlLX9E642Yfmur");

        try {
            response = EventPulseCloud.call("ListEvents", callArgs);
        } catch (HttpClientException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JsonException e) {
            e.printStackTrace();
        } 
      //See here I am running the loop in the background so its not on the main thread, then passing the list off to the onpostexecute that way all the main thread does is set the adapter list and notify it of the data update and the list should be updated on the screen
       if( response.get("Type").toString().equals("success")) {
            JsonArray records = null;
            try {
                records = response.getObject ("Data").getArray ("Records");
            } catch (JsonException e) {
                e.printStackTrace();
            }

            for(int i = 0; i < records.count(); i++) {
                JsonObject record = (JsonObject) records.get(i);
                sr1 = new SearchResults();
                sr1.setAddress(record.get("address").toString());
                resultList.add(sr1);
            }
        }  
        return resultList;
    }

    protected void onPostExecute(ArrayList<SearchResult> resultList) {
          setListItems(resultList);

    }   
}
}

add this line before the oncreate with all your other global var

   //here you want to create an adapter var with your base adapter so you can set it the updated list later when you have populated data from the internet
         ArrayList<SearchResults> searchResults = new ArrayList<SearchResults>();
         MyCustomBaseAdapter adapter = new MyCustomBaseAdapter(this, searchResults)

paste this over your oncreate method (replace it)

//here is just the code to update your main method to reflect all the changes I made
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    new LoginAction().execute("");  

    final ListView lv1 = (ListView) findViewById(R.id.ListView01);
    lv1.setAdapter(adapter);

}

and add this method to the adapter(MyCustomnBaseAdapter class) code

public void setListItems(ArrayList<SearchResult> newList) {
     searchArrayList = newList;
     notifyDataSetChanged();
}
Osman
  • 1,771
  • 4
  • 24
  • 47
  • 1
    I kept wondering why your list view was empty.. skipping frames isn't enough to result in an empty list view... it's because you never called 'notifyDataSetChanged();' on the adapter.. that's because it needs to know when you have changed the data in the adapter so it can reload(update).... So karakuri points out a few things you may not have realized... – Osman Jun 05 '13 at 22:03
  • I understand now.. Thank you.. But I got a problem, is about the return type of the doInBackground, I can't change it to `Arraylist` or `List` to return the List, the return has to be a String.. What should I do ? –  Jun 06 '13 at 00:42
  • Sorry I just meant that as idea code, let me modify it a little to see if i can get it working – Osman Jun 06 '13 at 01:15
  • It's cool, I have setted `AsyncTask>`.. but now I'm getting : `I/dalvikvm-heap(1273): Grow heap (frag case) to 8.925MB for 691216-byte allocation` And it doesn't display any result.. I've read that my program lunch an `outOfMemoryException` so how can I solve it ? Thank you :) –  Jun 06 '13 at 11:20
  • 2
    thats is fine, that just means your application is growing in size and thats understandable. You know how that loop was blocking up he main thread because it was so big, thats because you are downloading a ton of data which is causing android to grow the size of memory allocated for your app, that does not mean out of memory, just that your app is growing in size and thats fine and understandable. Now to address the fact you dont see anything in the listview, make sure you call `notifyDataSetChanged();` at some point in time, if you followed my outline it should be in the setListItems method – Osman Jun 06 '13 at 16:16
  • in the BaseAdapter class and called on post execute – Osman Jun 06 '13 at 16:16