Custom AsyncTask with callback - android

I need to write AsyncTask that will get reference on new object instances after the screen rotation. I wrote Callback interface and in JukeTask declared weak reference to this field.
JukeTask:
public abstract class JukeTask<Params, Progress, Result>
extends AsyncTask<Params, Progress, Result> {
private WeakReference<Callback<Progress, Result>> mWRCallback;
public void setCallback(Callback<Progress, Result> callback) {
mWRCallback = new WeakReference<>(callback);
}
#Override
protected void onPreExecute() {
Callback<Progress, Result> callback = mWRCallback.get();
if(callback != null) {
callback.onPreExecute();
}
}
#SuppressWarnings("unchecked")
#Override
protected void onProgressUpdate(Progress... values) {
Callback<Progress, Result> callback = mWRCallback.get();
if(callback != null) {
callback.onProgressUpdate(values);
}
}
#Override
protected void onPostExecute(Result result) {
Callback<Progress, Result> callback = mWRCallback.get();
if(callback != null) {
callback.onPostExecute(result);
}
}
public interface Callback<P, R> {
void onPreExecute();
#SuppressWarnings("unchecked")
void onProgressUpdate(P... values);
void onPostExecute(R result);
}
}
Example of using:
public class TestActivity extends AppCompatActivity implements JukeTask.Callback<Integer, Integer> {
private static final String TAG = TestActivity.class.getSimpleName();
private TextView mTextView;
JukeTask<Void, Integer, Integer> mTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
setContentView(mTextView);
mTextView.setText("Started");
//...
if(mTask == null) {
mTextView.setText("Task is null!");
mTask = new JukeTask<Void, Integer, Integer>() {
#Override
protected Integer doInBackground(Void[] params) {
int i = 0;
for (; i < 100000; ++i) {
Log.d(TAG, "Tick " + i);
}
//Do smth
return i;
}
};
mTask.setCallback(this);
mTask.execute();
}
}
#Override
public void onPreExecute() {
}
#Override
public void onProgressUpdate(Integer... values) {
}
#Override
public void onPostExecute(Integer result) {
mTextView.setText(result.toString());
}
}
Should I use WeakReference in JukeTask for Callback to not block a garbage collector?
How to save a reference to JukeTask? Because after the screen rotation it re-executes JukeTask.

Related

How do I get UI reference from AsyncTask in android

I am trying to learn MultiThreading in android although my code works fine when I print the data on Logcat window but my app crashes and gives me a null point exception whenever I try to update a component from the asynctask.
It gives me the following error
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.append(java.lang.CharSequence)' on a null object reference
at com.shivam.asynctasks.MainActivity.logm(MainActivity.java:41)
Line 41
textView.append(message + "\n");
My Code
public class MainActivity extends AppCompatActivity {
Button button ;
TextView textView;
String TAG = "MyTag";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button=findViewById(R.id.button);
textView=findViewById(R.id.TextView);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String TAG ="MyTag";
Log.d(TAG,"OnClick Thread Started");
MyTask mytask =new MyTask();
mytask.execute("Red","Black","Yellow","Blue","Orange");
}
});
}
public void logm(String message) {
Log.d(TAG, message);
textView.append(message + "\n");
}
}
class MyTask extends AsyncTask<String,String,String>{
#Override
protected String doInBackground(String... strings) {
String TAG = "MyTag";
for (String value :
strings) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "do in Background : "+value);
publishProgress(value);
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
MainActivity mainActivity =new MainActivity();
for (String color : values) {
mainActivity.logm(color);
}
}
}
You need to reference your activity not create new one. Here you can see.
import android.os.AsyncTask;
public class MainActivity extends AppCompatActivity {
Button button ;
TextView textView;
String TAG = "MyTag";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button=findViewById(R.id.button);
textView=findViewById(R.id.TextView);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String TAG ="MyTag";
Log.d(TAG,"OnClick Thread Started");
MyTask mytask =new MyTask(this);
mytask.execute("Red","Black","Yellow","Blue","Orange");
}
});
}
public void logm(String message) {
Log.d(TAG, message);
textView.append(message + "\n");
}
}
class MyTask extends AsyncTask<String,String,String> {
WeakReference<MainActivity> mainActivityRef;
public MyTask(MainActivity activity){
mainActivityRef = new WeakReference<MainActivity>(activity);
}
#Override
protected String doInBackground(String... strings) {
String TAG = "MyTag";
for (String value :
strings) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "do in Background : "+value);
publishProgress(value);
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
MainActivity mainActivity = mainActivityRef.get();
for (String color : values) {
mainActivity.logm(color);
}
}
You are getting the error because the you are creating the instance of the mainactivity class again inside the onProgressUpdate() method whose textView is not initiated. Thats why you are getting the null pointer exception as the textview field is not being initiated.
There is a simple solution to your problem. The onProgressUpdate() method runs on the main UI thread and you have the access to the textview variable inside the onProgressUpdate() method. so simply do the following:
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
MainActivity mainActivity =new MainActivity();
for (String color : values) {
textView.append(color + "\n");
}
}

Static AsyncTask ProgressBar

Sorry if this seems a basic question. I've updated Android Studio and notice some memory leak warnings on my AsyncTasks saying I should make them static. I have made them static but can't seem to make anything like List, ProgressBar, ImageView work without getting the same memory leak warning. It seems I can't win no matter which way I try it. I guess my questions are:
Are AsyncTasks supposed to be static? The official documentation doesn't make it static but my IDE fires warnings saying they should.
If they are meant to be static, how can I start and stop a ProgressBar within the static AsyncTask.
EDIT
This still throws "This AsyncTask class should be static or leaks might occur"
private class DownloadCategoryTask extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
String url = Config.API_URL +
"/Index.aspx?" +
"type=3&" +
"site_id=" + SITE_ID;
String method = "GET";
String array_name = "categories";
Downloaded_category_array = Config.getJSONNew(url, method, array_name, context);
return "";
}
protected void onPostExecute(String result) {
if(isCancelled()){
return;
}
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
//Update your UI here
//showProgressBar();
}
});
Populate_category_list();
}
}
Try this solution which I found:
public class MainActivity extends AppCompatActivity {
private ProgressBar progressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progress_bar);
}
public void startAsyncTask(View v) {
ExampleAsyncTask task = new ExampleAsyncTask(this);
task.execute(10);
}
private static class ExampleAsyncTask extends AsyncTask<Integer, Integer, String> {
private WeakReference<MainActivity> activityWeakReference;
ExampleAsyncTask(MainActivity activity) {
activityWeakReference = new WeakReference<MainActivity>(activity);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
MainActivity activity = activityWeakReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
activity.progressBar.setVisibility(View.VISIBLE);
}
#Override
protected String doInBackground(Integer... integers) {
for (int i = 0; i < integers[0]; i++) {
publishProgress((i * 100) / integers[0]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Finished!";
}
}
}
Reference: here
No, No need to make AsyncTasks as static.
If non static methods are trying to modify static members then IDE throws warning to make it static.
If you want to update your UI from AsyncTask use 'runOnUiThread'.
runOnUiThread(new Runnable() {
#Override
public void run() {
//Update your UI here
showProgressBar();
}
});
Looks like you are using anonymous inner class.
Here is the solution,
private class LoadData extends AsyncTask<Void, Void, String> {
LoadData() {
}
#Override
protected String doInBackground(Void... params) {
return "task finished";
}
#Override
protected void onPostExecute(String result) {
runOnUiThread(new Runnable() {
#Override
public void run() {
//Update your UI here
//showProgressBar();
}
});
}
}
//Execute your task
new LoadData().execute();

Update UI from AsyncTask and handle properly screen rotate

This code doesn't works when rotate screen.
I try to use Handler but messages are dispached to previous Activity(before rotate) and to new Activity.
¿How can a thread send messages to new Activity?
Please doesn't suggest avoid to rotate screen.
public class SampleActivity extends Activity {
TextView text;
Handler handler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
text = new TextView(this);
text.setText("HELLO");
layout.addView(text);
if (savedInstanceState != null) {
text.setText(savedInstanceState.getString("text"));
}
setContentView(layout);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("text", text.getText().toString());
}
#Override
protected void onStart() {
super.onStart();
new CounterTask().execute();
}
public class CounterTask extends AsyncTask<Void, String, Void> {
#Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
publishProgress("Hello " + i);
} catch (Exception e) {
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
String str = values[0];
text.setText(str);
}
}
}
You can run the AsyncTask in a retained fragment.
public class TaskFragment extends Fragment {
private Callback mCallback;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (Callback) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement TaskFragment.Callback");
}
}
#Override
public void onDetach() {
super.onDetach();
mCallback = null;
}
public void execute(){
new CounterTask().execute();
}
public interface Callback {
void onTaskUpdate(String value);
}
public class CounterTask extends AsyncTask<Void, String, Void> {
#Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
publishProgress("Hello " + i);
} catch (Exception e) {
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
if(mCallback != null) {
String str = values[0];
mCallback.onTaskUpdate(str);
}
}
}
}
Then, implement the callback in your activity and add the fragment via the fragment manager.
public class SampleActivity extends Activity implements
TaskFragment.Callback {
private static final String TAG_TASK_FRAGMENT = "task_fragment";
private TaskFragment mTaskFragment;
private TextView text;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
text = new TextView(this);
text.setText("HELLO");
layout.addView(text);
if (savedInstanceState != null) {
text.setText(savedInstanceState.getString("text"));
}
setContentView(layout);
FragmentManager fm = getFragmentManager();
mTaskFragment = (TaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT);
if(mTaskFragment == null){
mTaskFragment = new TaskFragment();
fm.beginTransaction()
.add(mTaskFragment, TAG_TASK_FRAGMENT)
.commit();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("text", text.getText().toString());
}
#Override
protected void onStart() {
super.onStart();
mTaskFragment.execute();
}
#Override
public void onTaskUpdate(String value) {
text.setText(value);
}
}
You could use AsyncTaskLoader. It will handle all rotations and lifecycle events of your activity/fragment.
Just add some configuration to prevent the recreating of activity when screen rotated, then everything's ok since there's only one instance of your activity.
Add the flowing line to your activity label in AndroidManifest.xml
android:configChanges="orientation|keyboardHidden|screenSize"
and this method to you SampleActivity.java
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// when screen rotated, this method will be called instead of onCreate
}

Android: call main activity method from Async onPostExecute method

Trying to call MainActivity method on onPostExecute(), but not getting any success. I tried so many things, but I think I am doing something wrong. processFinish() method on MainActivity is not called from onPostExecute().
//myClass.java
public class myClass extends Activity {
public AsyncResponse delegate=null;
public myClass(Context context){
this.mContext = context;
}
public interface AsyncResponse {
void processFinish(String output);
}
public class GetNotification extends AsyncTask<Integer, Void, String>{
public GetNotification() {
super();
}
#Override
protected String doInBackground(Integer... mArgs){
//code
}
#Override
protected void onPostExecute(String result){
super.onPostExecute(result);
delegate.processFinish(result);
}
#Override
protected void onPreExecute(){
}
}
public void getValue(int f){
m_flag = f;
if(Build.VERSION.SDK_INT >= 11){
new GetNotification().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, f);
}
else{
new GetNotification().execute(f);
}
}
}
//MainActivity.java
public class MainActivity extends Activity implements AsyncResponse{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//code
}
#Override
public void processFinish(String output){
getComActivity(output);
}
private void getData(String mURL){
this.getUrl=new com.sample.myClass(this);
getUrl.getValue(1);
}
public void getComActivity(String gStr){
if (gStr != null && gStr.trim() != ""){
Intent mIntent = new Intent(this.getApplicationContext(), myActivity.class);
this.startActivity(mIntent);
}
}
}
You need to initialze delegate. Change the code in myClass constructor.
public myClass(Context context){
delegate = (AsyncResponse) context;
}
public GetNotification() {
super();
}
//In your AsyncTask class
//use MainActivity or myClass instead of Activity
private Activity activity;
public GetNotification(Activity activity) {
this.activity = activity;
}
private void callMethod()
{
activity.some_method();
}
public class myClass extends Activity {
public AsyncResponse delegate=null;
Context mContext;
public myClass(Context context){
this.mContext = context;
}
public interface AsyncResponse {
void processFinish(String output);
}
public class GetNotification extends AsyncTask<Integer, Void, String>{
public GetNotification() {
super();
}
#Override
protected void onPreExecute(){
//start dialog progress over here
}
#Override
protected String doInBackground(Integer... mArgs){
//code
return null;
}
#Override
protected void onPostExecute(String result){
super.onPostExecute(result);
delegate.processFinish(result);
}
}
#SuppressLint("NewApi")
public void getValue(int f){
int m_flag = f;
if(Build.VERSION.SDK_INT >= 11){
new GetNotification().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, f);
}
else{
new GetNotification().execute(f);
if( MainActivity.activity!=null)
MainActivity.getComActivity("PassYourStringOverHere"); //here I am giving example how to call MainActivity method from other activity
}
}
public class MainActivity extends Activity implements AsyncResponse{
private myClass getUrl;
public static Activity activity=null;
public static Context context=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activity=this;//use to initialize the activity...later you can call...and start intent using this activity object...
context=this;//use for Intent...why I used this pattern because i made a static method so I have to do...or else it will show error...
//code
}
#Override
public void processFinish(String output){
getComActivity(output);
}
private void getData(String mURL){
this.getUrl=new com.sample.myClass(this);
getUrl.getValue(1);
}
public static void getComActivity(String gStr){
if (gStr != null && gStr.trim() != ""){
Intent mIntent = new Intent(context, myActivity.class);
activity.startActivity(mIntent);
}
}
}

In which case getFragmentManager() return null?

Problem : getFragmentManager() return null randomly.
Case: I have one activity with tab. On each tab press I'm replacing the content container with fragment. In case of two fragment I'm doing network hit to fetch data from a server. For that I have written the following code:
public class FetchMessagesyFragmentTask extends Fragment {
private static final String TAG_EXTRA = "tab_extra";
private static final String TAG = "Test";
private String mData;
public static final FetchMessagesyFragmentTask newInstance(String data) {
FetchMessagesyFragmentTask fragment = new FetchMessagesyFragmentTask();
Bundle bundle = new Bundle();
bundle.putString(TAG_EXTRA, data);
fragment.setArguments(bundle);
return fragment;
}
public static interface TaskCallbacks {
void onPreExecute();
void onCancelled();
void onPostExecute(MessageResponse response);
}
private TaskCallbacks mCallbacks;
private FetchMessage mTask;
private boolean mRunning;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(getTargetFragment() instanceof TaskCallbacks)) {
throw new IllegalStateException(
"Target fragment must implement the TaskCallbacks interface.");
}
mCallbacks = (TaskCallbacks) getTargetFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mData = getArguments().getString(TAG_EXTRA);
}
#Override
public void onDestroy() {
super.onDestroy();
cancel();
}
public void execute(String data) {
if (!mRunning) {
mTask = new FetchMessage();
mTask.execute(data);
mRunning = true;
}
}
public void cancel() {
if (mRunning) {
mTask.cancel(false);
mTask = null;
mRunning = false;
}
}
public boolean isRunning() {
return mRunning;
}
private class FetchMessage extends AsyncTask<String, Void, MessageResponse> {
private ProgressDialogFragment progressDialog;
#Override
protected void onPreExecute() {
progressDialog = new ProgressDialogFragment.Builder().setMessage(
"Please wait...").build();
progressDialog.show(((Fragment) mCallbacks).getFragmentManager(),
"task_progress");
mCallbacks.onPreExecute();
mRunning = true;
}
#Override
protected MessageResponse doInBackground(String... params) {
//Doing network hit here and returning value.
return value;
}
#Override
protected void onCancelled() {
mCallbacks.onCancelled();
mRunning = false;
}
#Override
protected void onPostExecute(MessageResponse response) {
if (mCallbacks != null) {
FragmentManager manager = ((Fragment) mCallbacks)
.getFragmentManager();
//XXXXXXXX GETTING MANAGER AS NULL HERE SOMETIME XXXXXXXXXXXXXX
progressDialog.dismiss(manager);
if (response != null) {
if (Integer.parseInt(response.getResponseCode()) == NetworkConstant.SUCCESS
&& response.getChatMessage() != null) {
saveDataToDb(response);
}
}
mCallbacks.onPostExecute(response);
mRunning = false;
}
}
private void saveDataToDb(MessageResponse response) {
//SaveToDB
}
}
}
I'm following this url to handle network hit on orientation change. I have commented the line where I'm getting the issue.
Note
This code work fine in normal situation but crash when I switch tabs very fast.

Categories

Resources