Cancel AsyncTask when choosing another menu item with Navigation Drawer

0

The Problem

I'm developing an app that has a side navigation menu ( Navigation Drawer ) as shown in the figure below.

Uploading some items from this menu requires an Internet request. Here everything works perfectly. When the menu item with this characteristic is selected, I execute an AsyncTask that performs the request, retrieves and updates the information in the View. However, if I choose another menu item without this AsyncTask being finalized, a situation is created that invalidates all the AsyncTask callback handling and results in application failure.

In the face of the problem reported above, I want to cancel AsyncTask execution when another menu option is chosen. How to implement this cancellation properly?

Implementation code of the above items

Menu Implementation

public class MainActivity extends AppCompatActivity implements FragmentDrawer.FragmentDrawerListener {
    private Toolbar mToolbar;
    private FragmentDrawer drawerFragment;

    ...

    public void onDrawerItemSelected(View view, int position) {
        Fragment fragment = null;
        String title = getString(R.string.app_name);
        switch (position) {
            case 0:
                fragment = new HomeFragment();
                title = getString(R.string.title_home);
                break;
            case 1:
                fragment = new FavoritosFragment();
                title = getString(R.string.title_favoritos);
                break;
            case 2:
                fragment = new ReclamacaoFragment();
                title = getString(R.string.title_reclamacoes);
                break;
            case 3:
                fragment = new ConfiguracoesFragment();
                title = getString(R.string.title_configuracoes);
                break;
            default:
                break;
        }

        if (fragment != null) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.container_body, fragment);
            fragmentTransaction.commit();

            getSupportActionBar().setTitle(title);
        }
    }

}

Running AsyncTask

public class FavoritosFragment extends ListFragment {

    public FavoritosFragment() {}

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_list, container, false);

        new LinhaFavoritaTask(this).execute();

        return rootView;
    }
}
    
asked by anonymous 04.09.2015 / 13:15

2 answers

1

For you to cancel AsyncTask , you must call the cancel() method of it.

You should have a method responsible for calling cancel() in your snippet (say this method is called cancelarCarregamentoDeDados() ) and this method must be public because it will be called from outside the snippet. This method should preferably be part of the fragment contract, ie the fragment must implement an interface that contains this method or extend an abstract fragment that forces its subclasses to implement this method (in the code I am giving preference to the second option). If your fragment already extends ListFragment , you should stop extending ListFragment and implement the features it offers to handle lists in order to extend the abstract fragment. My suggestion is that this abstract fragment is called ContentFragment or something like that, because it represents a fragment that occupies the main area of the screen (that is, it can be added to R.id.container_body ).

ContentFragment:

public abstract class ContentFragment extends Fragment {
    public abstract void cancelarCarregamentoDeDados();
}

FavoritesFragment:

public class FavoritosFragment extends ContentFragment {

    private LinhaFavoritaTask mTask;

    ...

    @Override
    public void cancelarCarregamentoDeDados() {
        if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) {
            mTask.cancel();
        }
    }
}

In order to call this method from Activity (which must be the bridge between fragments instead of one directly communicating with the other), the Activity must have a reference to the fragment. The best way to do this is to make Activity have a mCurrentContentFragment attribute of type ContentFragment and a public method setCurrentContentFragment(ContentFragment) :

Host:

public interface Host {
    public abstract void setCurrentContentFragment(ContentFragment fragment);
}

MainActivity:

public class MainActivity extends AppCompatActivity implements Host, ... {

    private ContentFragment mCurrentContentFragment = null;

    ...

    @Override
    public void setCurrentContentFragment(ContentFragment fragment) {
        this.mCurrentContentFragment = fragment;
    }

    ...
}
The setCurrentContentFragment() method should be called by the fragment itself within the onStart() method of the fragment (preferably the abstract fragment ContentFragment ) like this:

@Override
public void onStart() {
    Host activity = (Host)getActivity();
    activity.setCurrentContentFragment(this);
}

(Note that to avoid unnecessary coupling between the fragment and the MainActivity class, the setCurrentContentFragment() method must ideally belong to an interface, say Host , which is implemented by Activity ). >

In this way the life cycle of the fragment being replaced ( replaced ) on the screen will update the variable mCurrentContentFragment .

When you click on a Navigation Drawer item you can, before changing the new fragment, cancel loading the current one as follows:

   if (fragment != null) {

        if (mCurrentContentFragment != null) {
            mCurrentContentFragment.cancelarCarregamentoDeDados();
        }

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.container_body, fragment);
        fragmentTransaction.commit();

        getSupportActionBar().setTitle(title);
    }
    
04.09.2015 / 14:46
1

This post is meant to describe the adaptations that were made based on the @Piovezan response used to solve my problem.

In general, I have used the solution I posted here. However, I did not follow the suggestion below:

  

If your fragment already extends ListFragment, you should stop   extend ListFragment and implement the features that it   offers to treat lists, in order to be able to extend the fragment   abstract.

For convenience, I continued to extend fragments that have a listing from ListFragment . I decided to proceed in this way for the facilities offered by this class when working with data listing.

Summarizing the adapted solution

I created two abstract classes used by my fragments. A specific one for the fragments that use listing, which extends ListFragment . And a more generic one as suggested in the @ Piovezan answer. These classes contain by default the implementation of the onStart() method. Another change is that AsyncTask 's operation cancellation method was included in an interface. Therefore, my abstract classes implement a contract that forecloses a AsyncTask .

Implementation

Generic usage class for a Fragment

public abstract class ContentFragment extends Fragment implements TaskCancelable {

    @Override
    public void onStart() {
        super.onStart();
        Host activity = (Host) getActivity();
        activity.seCurrentFragment(this);
    }
}

Specific use class for a ListFragment

public abstract class ListContentFragment extends ListFragment implements TaskCancelable {

    @Override
    public void onStart() {
        super.onStart();
        Host activity = (Host) getActivity();
        activity.seCurrentFragment(this);
    }
}

Agreement used to cancel AsyncTask

public interface TaskCancelable {
    void cancelTaskOperation();
}

Example of a fragment using ListFragment

public class FavoritosFragment extends ListContentFragment {

    private LinhaFavoritaTask task;

    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_list, container, false);

        task = new LinhaFavoritaTask(this);
        task.execute();

        return rootView;
    }

    @Override
    public void cancelTaskOperation() {
        if (task != null && task.getStatus() == AsyncTask.Status.RUNNING) {
            task.cancel(true);
        }
    }
}

The main class: responsible for loading the menu

public class MainActivity extends AppCompatActivity implements Host, FragmentDrawer.FragmentDrawerListener {

    private TaskCancelable mCurrentContentFragment = null;

    ...

    private void displayView(int position) {
        Fragment fragment = null;
        String title = getString(R.string.app_name);
        switch (position) {
            case 0:
                fragment = new HomeFragment();
                title = getString(R.string.title_home);
                break;
            case 1:
                fragment = new FavoritosFragment();
                title = getString(R.string.title_favoritos);
                break;
            case 2:
                fragment = new ReclamacaoFragment();
                title = getString(R.string.title_reclamacoes);
                break;
            case 3:
                fragment = new ConfiguracoesFragment();
                title = getString(R.string.title_configuracoes);
                break;
            default:
                break;
        }

        if (fragment != null) {

            if (mCurrentContentFragment != null) {
                mCurrentContentFragment.cancelTaskOperation();
            }

            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.container_body, fragment);
            fragmentTransaction.commit();

            // set the toolbar title
            getSupportActionBar().setTitle(title);
        }
    }

    @Override
    public void seCurrentFragment(TaskCancelable fragment) {
        this.mCurrentContentFragment = fragment;
    }
}

.

    
05.09.2015 / 18:56