Search code examples
javaandroidandroid-asynctask

Cannot use asyncTask.cancel ()


I have a dataGetter class in which I load the necessary data (part of the url address, email, ...) and then call AsyncTask. I use a drawer menu where every single fragment calls, when creating, a dataGetter from a new thread. If I quickly switch between fragments (before AsyncTask completes), the data will mix (in the new fragment I will release the data from the previous one). I've tried using AsyncTask.cancel, but I always get an error.

public class dataGetter {

    @SuppressLint("StaticFieldLeak")
    public static Context context;
    private static String siteURL;
    private static String domain;
    private static boolean login;
    private static String email;
    private static String password;
    private static AsyncRetrieve asyncRetrieve;

    public static String getData(Context context, String url, String domain, boolean login, String email, String password) {
        dataGetter.context = context;
        dataGetter.siteURL = url;
        dataGetter.domain = domain;
        dataGetter.login = login;
        dataGetter.email = email;
        dataGetter.password = password;

        String result = "";
        try {
            if (asyncRetrieve != null)
                asyncRetrieve.cancel(true);
            asyncRetrieve = new AsyncRetrieve();
            result = asyncRetrieve.execute().get();
        }
        catch (ExecutionException | InterruptedException e) {
            Sentry.capture(e);
            e.printStackTrace();
        }

        return result;
    }

    public static class AsyncRetrieve extends AsyncTask<String, Void, String> {
        HttpsURLConnection conn;
        URL url = null;

        // This method does not interact with UI, You need to pass result to onPostExecute to display
        @Override
        protected String doInBackground(String... params) {
            .....
        }

    }

}

Any call to asyncRetrieve.cancel () returns the following error

E/AndroidRuntime: FATAL EXCEPTION: Thread-9
    Process: com.example.wedos, PID: 6709
    java.util.concurrent.CancellationException
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:193)
        at android.os.AsyncTask.get(AsyncTask.java:542)
        at com.example.xyz.dataGetter.getData(dataGetter.java:45)
        at com.example.xyz.ui.domains.DomainsFragment.getDetails(DomainsFragment.java:195)
        at com.example.xyz.ui.domains.DomainsFragment.access$100(DomainsFragment.java:36)
        at com.example.xyz.ui.domains.DomainsFragment$1.run(DomainsFragment.java:74)
        at java.lang.Thread.run(Thread.java:764)

I tried to search the internet, but unfortunately nothing. Would anyone advise me how to cancel the asynctask so that the data from the previous fragment does not appear in the new?

Correct resultIncorrect result

Thank you very much in advance

EDIT

First fragment "domains"

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    //new ViewModelProvider(this).get(DomainsViewModel.class);
    root = inflater.inflate(R.layout.fragment_domains, container, false);
    context = root.getContext();
    setHasOptionsMenu(true);

    models = new ArrayList<>();
    result = new ArrayList<>();

    progressBar = root.findViewById(R.id.loading);
    viewPager2 = root.findViewById(R.id.viewPager);
    tabLayout = root.findViewById(R.id.tabs);
    linearLayout = root.findViewById(R.id.DomainsLinearLayout);

    thread = new Thread(new Runnable() {
        @Override public void run() {
            try {
                getData();
            } catch (JSONException e) {
                Sentry.capture(e);
                e.printStackTrace();
            }
            for (String domena : result) {
                try {
                    getDetails(domena);
                } catch (JSONException e) {
                    Sentry.capture(e);
                    e.printStackTrace();
                }
            }
        }
    });
    thread.start();

    check();

    return root;
}

@Override
public void onDestroy() {
    super.onDestroy();
    asyncRetrieve.cancel(true);
    System.out.println("On Destroy");
    if (thread.isAlive())
        thread.interrupt();
}

Second fragment "cash"

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    //new ViewModelProvider(this).get(CashViewModel.class);
    View root = inflater.inflate(R.layout.fragment_cash, container, false);
    context = root.getContext();
    setHasOptionsMenu(true);

    textView = root.findViewById(R.id.cash_amount);
    progressBar = root.findViewById(R.id.loading);
    linearLayout = root.findViewById(R.id.CashLinearLayout);
    Button cashHistory = root.findViewById(R.id.showHistory);
    cashHistory.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int index = result.indexOf(' ');
            Intent i = new Intent(context, CashHistoryActivity.class);
            i.putExtra("CURRENCY", result.substring(index));
            startActivity(i);
        }
    });

    thread = new Thread(new Runnable() {
        @Override
        public void run() {
            getData();
        }
    });
    thread.start();

    return root;
}

getData() method

result = dataGetter.getData(context, "cashCheck", "", false, "", "");

Latest error

I/System.out: On Destroy
E/AndroidRuntime: FATAL EXCEPTION: Thread-12
    Process: com.example.wedos, PID: 9115
    java.util.concurrent.CancellationException
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:193)
        at android.os.AsyncTask.get(AsyncTask.java:542)
        at com.example.wedos.dataGetter.getData(dataGetter.java:45)
        at com.example.wedos.ui.cash.CashFragment.getData(CashFragment.java:90)
        at com.example.wedos.ui.cash.CashFragment.access$200(CashFragment.java:25)
        at com.example.wedos.ui.cash.CashFragment$2.run(CashFragment.java:59)
        at java.lang.Thread.run(Thread.java:764)
D/io.sentry.android.event.helper.AndroidEventBuilderHelper: Proguard UUIDs file not found.
W/System.err: SLF4J: Failed to load class "org.slf4j.impl.StaticMDCBinder".
    SLF4J: Defaulting to no-operation MDCAdapter implementation.
    SLF4J: See http://www.slf4j.org/codes.html#no_static_mdc_binder for further details.
W/System.err: java.lang.InterruptedException
        at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:420)
        at java.util.concurrent.FutureTask.get(FutureTask.java:192)
        at android.os.AsyncTask.get(AsyncTask.java:542)
        at com.example.wedos.dataGetter.getData(dataGetter.java:45)
        at com.example.wedos.ui.domains.DomainsFragment.getDetails(DomainsFragment.java:198)
        at com.example.wedos.ui.domains.DomainsFragment.access$100(DomainsFragment.java:38)
        at com.example.wedos.ui.domains.DomainsFragment$1.run(DomainsFragment.java:76)
        at java.lang.Thread.run(Thread.java:764)
I/Process: Sending signal. PID: 9115 SIG: 9

Solution

  • I recommend dropping the dataGetter class and removing the extra Thread that runs the AsyncTask. As the way you handle the API response depends on the fragment, you should create a public base class, e.g. AsyncReceiver in a seperate file.

    public class AsyncRetrieve extends AsyncTask<String, Void, String> {
    
        private Context context;   
        private Domain domain;
        // etc.
    
        public AsyncRetrieve(Context context, String domain, boolean login, String email, String password) {
            this.context = context;
            this.url = url;
            this.domain = domain; 
            // etc.
        }
    
        @Override
        public String doInBackground(String... params) {
            return fetchData("domainList");
        }
    
        private String fetchData(String url) {
            // do what you originally did in the old doInBackground
            // of the old AsyncRetriever class, but use the url argument
            // passed to this method
            return result;
        }
    }
    
    

    Then, in your fragments add a private extended subclass of AsyncReceiver that implements the onPostExecute method. That's where you handle the API response and update your views accordingly.

    For example:

    public class DomainsFragment extends Fragment {
    
        private DomainsAsyncReceiver receiver;
        // example text view that is updated on results
        private TextView tv_results;
    
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // instantiate your views
            // e.g.
            tv_results = findViewById(R.id.tv_results);
    
            // etc.
    
            // instantiate the async task
            receiver = new DomainsAsyncReceiver(domain, url, etc.);
            receiver.execute();
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            // cancel the async task
            receiver.cancel(true);
        }
    
        private class DomainsAsyncReceiver extends AsyncRetrieve {
    
            @Override
            protected String doInBackground(String... params) {
                String result = fetchData("domainList");
                // do something with the result, e.g.
                String details = fetchData("domainDetails");
                return details;
            }
    
            @Override
            protected void onPostExecute(String result) {
                super.onPostExecute(result);
                tv_results.setText(result);
            }
        }
    }