arjacsoh
arjacsoh

Reputation: 9232

Execute AsyncTask in a headless Fragment

I have read that executing AsyncTask in an Activity has the disadvantage that the task does not cope well with configuration changes, since the AsyncTask's lifecycle is not tied on the Activity lifecycle. Consequently, if the task should update the UI of an Activity but in the meantime the Activity has been destroyed due to a configuration change, the AsyncTask does not know anything of the newly created Activity and therefore it has no chance to update it. That can lead to memory leak as well.

The workaround proposed it to use an headless Fragment (Fragment with no UI), just to execute the AsyncTask, because it can survive configuration changes. I do not comprehend though, how it could be a viable approach, since the Activity and its views have been destroyed in any case. Example:

  public MyFragment extends Fragment {
        private SomeAsync myAsync = new SomeAsync();
        private WeakReference<View> someActivityView;

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

        public void setActivityView(View View) {
            someActivityView = new WeakReference<View>(view);
        } 

        ...

        class SomeAsync extends AsyncTask<Void, Integer, Object> {

        protected Object doInBackground(Void... args) {
         ...
        }

        protected void onPostExecute(Object result) {
             doUpdate(someActivityView, result);
         }

         }
       }

The someActivityView is a View which belongs to the Activity and it is set to the Fragment to be updated. I think the someActivityView is not preserved in case of a configuration change while the Activity is created again, hence what is the benefit of using a headless Fragment in this case?

Upvotes: 0

Views: 1383

Answers (1)

Budius
Budius

Reputation: 39836

yes, what you read about AsyncTask is true, yes viewLess fragment is a way around it.

the general idea on doing something with the fragment is that it has access to whichever activity is the current active activity. That way, instead of storing the actual view using public void setActivityView(View View) { you should only store the id of this view and find it in the activity whenever you need it.

I'll copy your code and change a few things on it to show the result. But there're other ways of doing it. I'll briefly talk about them at the end.

  public MyFragment extends Fragment {
        private SomeAsync myAsync = new SomeAsync();
        private int viewId = -1;

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

            // restore the view id
            if(savedInstanceState != null && savedInstanceState.containsKey("myViewId")){
                  viewId = savedInstanceState.getInt("myViewId");
            }
        }

        @Override
        public void onSaveInstanceState (Bundle outState) {
             super.onSaveInstanceState(outState);
             // save the view ID
             if(viewId > 0) {
                outState.putInt("myViewId", viewId);
             }
        }

        public void setActivityView(View View) {
            viewId = view.getId();
        } 

        ...

        class SomeAsync extends AsyncTask<Void, Integer, Object> {

        protected Object doInBackground(Void... args) {
         ...
        }

        protected void onPostExecute(Object result) {
             Activity activity = getActivity();
             if(viewId > 0 && activity != null) {
                 View view = activity.findViewById(viewId);
                 doUpdate(view, result);
             }
         }

         }
       }

And yes, it's necessary to save the ID because there're other reasons your activity might be killed (e.g. goes to background and system runs out of memory);

Alternatives:

  1. put the view ID directly into the fragment setArguments(bundle).
  2. Make the activity implement an interface with method that receives your AsyncTask. Then onPostExecute you call getActivity, cast to this interface and pass the result directly to the activity.
  3. (that alternative is my favorite because it's the cleanest): Do not use AsyncTask and do not tie together processing result with UI components. Create a proper structure with data, processing and UI separation. UI elements should only be updated based on subscriptions to data elements. There're several patterns that can help with separation of responsabilitites. MVP, MVC, MVVM, etc. Pick one.

Upvotes: 2

Related Questions