Cancel Asynctask when choosing another menu item with Navigation Drawer

Asked

Viewed 109 times

0

The Problem

I’m developing an app that has a side navigation menu (Navigation Drawer) as shown below.

inserir a descrição da imagem aqui

Loading some items from this menu requires an Internet request. Here at this point everything works perfectly. When choosing the menu item with this feature I run an Asynctask that performs the request, retrieves and updates the information in the View. But if I choose another menu item without this Asynctask being finalized, a situation is created that invalidates all treatment performed in the Asynctask callback and results in application failure.

Faced with the problem reported above, I want to cancel the Asynctask execution when another menu option is chosen. How to implement this cancellation properly?


Implementation code of the mentioned 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);
        }
    }

}

Execution of the 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;
    }
}

2 answers

1

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

In general I used in essence the solution posted by him here. However, I did not follow the suggestion below:

If your fragment already extends Listfragment, you should stop extend Listfragment and implement in hand the functionalities that it offers to treat lists, so you can extend the fragment abstract.

For convenience reasons I continued to extend the fragments that present a listing from ListFragment. I decided to do so for the facilities offered by this class when working with data listing.

Summarizing the adapted solution

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

Implementation

Generic use 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;
    }
}

.

1


For you to cancel the AsyncTask, should call the method cancel() of the same.

Must have a method responsible for calling cancel() in its fragment (let’s say this method is called cancelarCarregamentoDeDados()) and this method must be public, for it will be called out of the fragment. Preferably this method should be part of the fragment contract, that is, the fragment should implement an interface that contains this method or extend an abstract fragment that forces its subclasses to implement this method (in the code I’m giving preference to the second option). If your fragment already extends ListFragment, you must stop extending ListFragment and implement in hand the features it offers to treat lists, so that you can extend the abstract fragment. My suggestion is that this abstract fragment be called ContentFragment or something similar, because it represents a fragment that occupies the main area of the screen (that is, which can be added to the R.id.container_body).

Contentfragment:

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

Favoritosfragment:

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 the Activity (which must be the bridge between the fragments, rather than one communicating directly with the other), the Activity need to have a reference to the fragment. The best way to do this is by making the Activity have an attribute mCurrentContentFragment 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 method setCurrentContentFragment() should be called by the fragment itself within the method onStart() of the fragment (preferably the abstract fragment ContentFragment) thus:

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

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

Thus the very life cycle of the fragment being exchanged (replaced) on the screen takes care of updating the variable mCurrentContentFragment.

Done all this, whenever you click on an item of the Navigation Drawer can before changing the new fragment cancel loading the current one so:

   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);
    }
  • Piovezan, your solution is great! It worked perfectly. I decided not to give up the simplicities of the use of Listfragment and for this I dismembered a little its solution to meet my need, but in essence it is the same solution. I will post here to include and to be an alternative to those who want to continue using the listing with Listfragment.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.