2

I have a tab fragment inside of a container activity.

I would like to download some data to display in the tab. In the tab, I have made an asynctask that I execute just after inflating the fragment layout in onCreateView. When I do it this way, the AsyncTask's doInBackground... work occurs on the main thread and the view does not load until it is done. All of my progress... logs show up at the same time as soon as the task is done.

However, if I put a button in the fragment layout and start the asynctask work as a response to the button click, it works as expected, I get my normally spaced progress updates.

So, why does AsyncTask run on the main thread if it is the first thing to happen when my fragment starts? And, how can I prevent this? My ultimate goal is to show a progress wheel while the data is downloading.

Edit. Here is where I call my AsyncTask.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    final View rootView = inflater.inflate(R.layout.temp_my_moments, container, false);

    getMyClips(getActivity(), rootView, p_session, p_person,progressBar);

//If I use this button, it works fine:
//    test = (Button) rootView.findViewById(R.id.testbutton);
//    test.setOnClickListener(new View.OnClickListener() {
//        @Override
//        public void onClick(View v) {
 //           progressBar =(ProgressBar) rootView.findViewById(R.id.myMomentsProgress);
  //          getMyClips(getActivity(), rootView, p_session, p_person, progressBar);
  //      }
 //   });
    return rootView;
}

And here is getMyClips():

public void getMyClips(Context context,View view,String thisSession,String thisPerson,ProgressBar progressBar) {
    Log.d("Currently running", "getMyClips");
        JSONObject params = new JSONObject();
        try {
            params.put("Function", clipsMine_apiCall);
            params.put("p_session", thisSession);
            params.put("p_person", thisPerson);
        } catch (JSONException e1) {
            e1.printStackTrace();
        }

        ApiClipCaller webServiceTask = new ApiClipCaller(context,view,progressBar);
        if (webServiceTask.hasConnection()) {
            webServiceTask.execute(params);
        } else {
                //TODO no internet connection
        }
}

Edit Here is my AsyncTask. This is inside the Fragment:

public class ApiClipCaller extends AsyncTask<JSONObject, Integer, ApiResponse> {
public Context context;
public String clipID;
public String sessionID;
public String personID;
public String functionName;
public View view;
public ProgressBar progressBar;
public final String apiURL = "....";  //need to keep private
LinearLayout loading;

public ApiClipCaller(Context c, View v,ProgressBar progressBar) {
this.context = c;
this.view = v;
this.progressBar = progressBar;
loading = (LinearLayout) view.findViewById(R.id.loadingMyMoments);
}

    @Override 
    protected ApiResponse doInBackground(JSONObject... params) {
        JSONObject realParams = params[0];
        try {
            functionName = realParams.getString("Function");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        ApiResponse responseObject = masterFunction(realParams);

        //making some work to see if it is running correctly
        for (int i=10;i<=100;i += 10)
        {
            try {
                Thread.sleep(1000);
                publishProgress(i);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return responseObject;
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        loading.setVisibility(View.VISIBLE);
        progressBar.setProgress(progress[0]);
        Log.d("progress",Integer.toString(progress[0]));
    }

    @Override
    protected void onPostExecute(ApiResponse responseObject) {
        loading.setVisibility(View.GONE);
        if (functionName.equals(clipsMine_apiCall)) {
            JSONObject clipObject = responseObject.getResponseJSONObject();

           //just testing here to see if I can get data back in the fragment view when it is done. 
///This works as expected.
  String responseString = responseObject.getResponseString();
             TextView showAsyncResults = (TextView) view.findViewById(R.id.testAsyncTask);
             showAsyncResults.setText(responseString);
        }
        super.onPostExecute(responseObject);
    }
}
fullmeriffic
  • 236
  • 2
  • 14
  • 3
    Show us your AsyncTask code. – Chris Stillwell Jun 02 '15 at 15:27
  • 1
    I'm not sure why this question deserves so many downvotes? – Sergei Rodionov Jun 02 '15 at 15:38
  • And yes I read this http://stackoverflow.com/questions/4918781/why-does-asynctask-run-in-the-main-thread-of-an-application?rq=1 as well as some others. None of it helped – fullmeriffic Jun 02 '15 at 15:39
  • 2
    Show us how you are calling the asyncTask in your fragment. Are you calling doInBackground directly? – ElDuderino Jun 02 '15 at 15:49
  • @ElDuderino OK, I have updated it. As you can see, I just call it with .execute(), not directly – fullmeriffic Jun 02 '15 at 15:58
  • 1
    So what exactly are you interested in? An explanation of this phenomena (so that it doesn't happen again)? Or in-depth answer on why the `AsyncTask` was designed this way? – Ordous Jun 02 '15 at 16:33
  • @ordous I would 1) like to understand why it is happening this way and 2) get a way to execute the doInBackground... work, well, in the background, if it occurs when the view is loaded (so I don't have to make the user click a button or stare at a blank screen). Thanks in advance – fullmeriffic Jun 02 '15 at 16:37
  • 1
    @fullmeriffic The javadoc on the `execute(...)` method explicitly states: `This method must be invoked on the UI thread`. You're breaking the contract of the method by invoking it on the wrong thread, hence you get incorrect behaviour. If you want to start your `AsyncTask` from the main thread - post it as an event on the UI thread (honestly I don't know what method does that, but my experience with Swing tells me it should be quite easy) – Ordous Jun 02 '15 at 16:46
  • @ordous ok thanks. I assumed that onCreateView was on the UI thread, but I guess not? How would you recommend I start AsyncTask on app startup since this does not work? – fullmeriffic Jun 02 '15 at 16:50
  • 1
    @fullmeriffic This is getting close to the limits of my knowledge of Android (I've never coded for it...). From what I can gather `onCreateView` is not actually run on the UI thread. You could post the task via `activity.runOnUiThread`. However, I'd be wary of this - it may mean that your program can get a partial result from the `AsyncTask` before the UI has finished initializing. Another option would be to use a different lifecycle stage - for example `onResume` instead of `onCreate` (I think the UI is guaranteed to be in place at that time) – Ordous Jun 02 '15 at 17:02

1 Answers1

0

So, it seems like the answer is that I was going about this all wrong. It it is not possible to launch asynctask when the first activity or fragment loads, because the onCreate etc. methods are not actually on the UI thread. So, AsyncTask cannot be executed directly from there, as Ordous pointed out.

Instead, it seems like the solution is to load information when the application first starts by creating a class which extends Application and doing the loading work from there, because this will necessarily be on the main thread. The methods in there can be called from the activity to access the layouts and make a progress bar or similar.

Reference: http://developer.android.com/reference/android/app/Application.html A good example: http://www.intertech.com/Blog/androids-application-class/

fullmeriffic
  • 236
  • 2
  • 14