Question

EDIT: tl;dr How do you insert into db using content resolver and simpleCursorAdapter without a listfragment hanging?

I have an AsyncTask that I am using to download JSON data. In onPostExecute() of that task I call handleNewMessageReponse() to update my Sqlite database with the results.

The code for handleNewMessageResponse()

private void handleNewMessagesResponse() {

    getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {

     //Step 1. Ensure we got something back.
        if (mMessagesResponse.getMessages().length > 0){

            //Step 2. Delete all Message DB rows.
            getActivity().getContentResolver().delete(EntegraContentProvider.CONTENT_URI_MESSAGES, null, null);

            //Step 3. Populate DB with new info.
           //Hangs UI here.
            for (int i = 0; i < mMessagesResponse.getMessages().length; i++) {
                Messages messages = mMessagesResponse.getMessages()[i];

                ContentValues values = new ContentValues();
                values.put("id", messages.getId());
                values.put("parent", messages.getParent());
                values.put("type", messages.getType());
                values.put("user", messages.getUser());
                values.put("subject", messages.getSubject());
                values.put("body", messages.getBody());
                values.put("datetime", messages.getDatetime());
                values.put("status", messages.getStatus());
                values.put("touched", messages.getTouched());

                //Perform the Insert.
                getActivity().getContentResolver().insert(
                        EntegraContentProvider.CONTENT_URI_MESSAGES, values);

                mWasDataLoaded = true;

            }

        }

            mDataListener.onDataFetchComplete();

        }

    });
}

This is all contained in a ListFragment which implements LoaderManager.LoaderCallbacks

The onCreate of this fragment looks like this:

    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    String[] uiBindFrom = { "user", "subject" };
    int[] uiBindTo = { R.id.messageUser , R.id.messageTitle };

    getLoaderManager().initLoader(MESSAGES_LIST_LOADER, null, this);

    adapter = new SimpleCursorAdapter(
            getActivity(), R.layout.messages_list_item,
            null, uiBindFrom, uiBindTo,
            CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

    setListAdapter(adapter);

}

Everything works great with one exception the below section of the code takes up to 10 seconds to execute and hangs the UI/List during the 10 seconds.

this section is when UI hangs:

//Step 3. Populate DB with new info.
            for (int i = 0; i < mMessagesResponse.getMessages().length; i++) {
                Messages messages = mMessagesResponse.getMessages()[i];

                ContentValues values = new ContentValues();
                values.put("id", messages.getId());
                values.put("parent", messages.getParent());
                values.put("type", messages.getType());
                values.put("user", messages.getUser());
                values.put("subject", messages.getSubject());
                values.put("body", messages.getBody());
                values.put("datetime", messages.getDatetime());
                values.put("status", messages.getStatus());
                values.put("touched", messages.getTouched());

                //Perform the Insert.
                getActivity().getContentResolver().insert(
                        EntegraContentProvider.CONTENT_URI_MESSAGES, values);

Can someone plese point me in the right direction and tell me what the standard approach to this seemingly common task is? This should be pretty simple but am having a hard time finding examples that fit my use case.

Thanks.

Here is my final code. Much more fluid:

private class EventsFetcher extends AsyncTask<String, Void, EventsResponse> {
    private static final String TAG = "EventsFetcher";
    @Override
    protected EventsResponse doInBackground(String... params) {
        EventsResponse returnResponse = null;
        try {
            //Create an HTTP client
            HttpClient client = new DefaultHttpClient();
            HttpPost post = new HttpPost(mEventsBaseUrl);

            //Perform the request and check the status code
            HttpResponse response = client.execute(post);
            StatusLine statusLine = response.getStatusLine();
            if(statusLine.getStatusCode() == 200) {
                HttpEntity entity = response.getEntity();
                InputStream content = entity.getContent();

                try {
                    //Read the server response and attempt to parse it as JSON
                    Reader reader = new InputStreamReader(content);

                    Gson gson = new Gson();

                    returnResponse=  gson.fromJson(reader, EventsResponse.class);

                    content.close();

                } catch (Exception ex) {
                    Log.e(TAG, "Failed to parse JSON due to: " + ex);
                    failedGettingEvents();
                }
            } else {
                Log.e(TAG, "Server responded with status code: " + statusLine.getStatusCode());
                failedGettingEvents();
            }
        } catch(Exception ex) {
            Log.e(TAG, "Failed to send HTTP POST request due to: " + ex);
            failedGettingEvents();
        }
        return returnResponse;
    }

    @Override
    protected void onPostExecute(EventsResponse resultResponse) {
        EventsResultsHandler resultsHandler = new EventsResultsHandler();
        resultsHandler.execute(resultResponse);
    }
}

private class EventsResultsHandler extends AsyncTask<EventsResponse, Void, String> {
    private static final String TAG = "EventsResultsHandler";

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

        try {

            EventsResponse evResponse = params[0];

            //Step 1. Ensure we got something back.
            if (evResponse.getEvents().length > 0){

                //Step 2. Populate Array with new info.
                List<ContentValues> jsonResults = new ArrayList<ContentValues>();
                for (int i = 0; i < evResponse.getEvents().length; i++) {

                    Events events = evResponse.getEvents()[i];
                    ContentValues values = new ContentValues();

                    values.put("start_date", events.getStart_date());
                    values.put("end_date", events.getEnd_date());
                    values.put("name", events.getName().replace("'","''"));
                    values.put("location", events.getLocation().replace("'","''"));
                    values.put("summary", events.getSummary().replace("'","''"));
                    values.put("lat", events.getLat());
                    values.put("lng", events.getLng());
                    values.put("is_active", events.getIs_active());
                    values.put("hide_calendar_button", events.getHide_calendar_button());
                    values.put("hide_map", events.getHide_map());

                    jsonResults.add(values);

                }

                //Step 3. Delete all data in table.
                getActivity().getContentResolver().delete(EntegraContentProvider.CONTENT_URI_EVENTS, null, null);

                //Step 4. Insert new data via via Bulk insert.
                getActivity().getContentResolver().bulkInsert(EntegraContentProvider.CONTENT_URI_EVENTS, jsonResults.toArray(new ContentValues[jsonResults.size()]));

                mWasDataLoaded = true;

            }

        } catch(Exception ex) {
            Log.e(TAG, "Failed to update results due to: " + ex);
            failedGettingEvents();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        mDataListener.onDataFetchComplete();
    }
}
Was it helpful?

Solution

You don't want to run the DB update on the UI thread since that will hang the UI as you saw.

But you method of updating is likely to cause the problems you are describing because you are using a cursor adapter.

You are effectively clearing the database and repopulating. If the cursor adapter gets to run before you have rewritten all rows you will see the result you are talking about in your second comment above.

You have two choices, the first is to load to memory, and then quickly swap the list adapters internal list by swapping a pointer to the list data in the adapter and then telling the list adapter that things changed. You have to do this swap on the UI thread but since it is just updating a pointer to the data it is fast and keeps the UI responsive. The downside is you have to have two copies of the data in memory to do the swap.

The second and perhaps harder is to change the way that you sync with the server so that you don't have to clear everything and reload everything, but rather are deleting, adding or modifying single records. I recommend this route but it is probably more work for you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top