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?
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
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);
}
}
}