I'm with a little problem that is driving me crazy. I'm using a AsyncTask in a retained Fragment to update a progressDialog in my activity. I'm using callbacks to send the progress from my fragment.
The problem is: When I rotate my screen it simply stops do update the progressDialog in the recreated activity. It seems like onProgressUpdate stops to being called in the rotated activity.
the relevant part of the code is shown below:
Worker Fragment
public class WorkerFragment extends Fragment {
Context mContext;
...
public static interface TaskCallbacks {
void onPreExecute();
void onProgressUpdate(int... progress);
void onPostExecute();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (TaskCallbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
}
//inner class
class DownloadFileFromURL extends AsyncTask<String, Int, Void> {
#Override
public void onPreExecute() {
if (mCallbacks != null) {
mCallbacks.onPreExecute();
}
#Override
public String doInBackground(String... f_url) {
... //some verifications of files
if(!localFile.exists()){
//http connection, check, buffer, inputstream, etc...
publishProgress((int)((bytesDownloaded*100)/remoteFileSize));
...
}
}
#Override
protected void onProgressUpdate(int... percent) {
if (mCallbacks != null) {
mCallbacks.onProgressUpdate(percent);
}
}
...
}
}
and the Activity
public class Updater extends Activity implements WorkerFragment.TaskCallbacks {
private WorkerFragment myWorker;
private ProgressDialog pDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_updater);
pDialog=null;
showDialog(1);
FragmentManager fm = getFragmentManager();
myWorker = (WorkerFragment) fm.findFragmentByTag("task");
if(myWorker == null)
{
myWorker = new WorkerFragment();
fm.beginTransaction().add(myWorker, "task").commit();
}
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case 1:
pDialog = new ProgressDialog(this);
pDialog.setMessage("Baixando arquivos de mÃdia");
pDialog.setIndeterminate(false);
pDialog.setMax(100);
pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pDialog.setCancelable(false);
pDialog.show();
dialogType=progress_bar_type;
return pDialog;
}
}
#Override
public void onProgressUpdate(int... progress) {
//setting progress percentage
if(pDialog!=null)
{
pDialog.setProgress(progress[0]);
}
}
#Override
public void onPostExecute(){
if(pDialog!=null)
{
dismissDialog(1);
...
}
}
Try to put this code in manifest, where you've indicated your activity
android:configChanges="orientation|screenSize"
Related
I have two fragments in my Activity : Fragment_A and Fragment_B.
In Fragment A, I created an AsyncTask (when the user "swipeRefreshes" the screen). In the onPostExecute() of this task, I want to display a Toast :
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
MakeRequestTask() {
//Some stuff
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
}
#Override
protected void onCancelled() {
swipeRefreshLayout.setRefreshing(false);
//Some stuff
}
}
If the user changes from Fragment_A to Fragment_B before the AsyncTask finishes, I get a crash:
java.lang.IllegalStateException: Fragment Fragment_A not attached to a context.
I know how to avoid the crash (by adding the condition isAdded()), but I want my Toast to be displayed no matter which Fragment is displayed/alive on top of my Activity.
1stly I would like to suggest you, please make your MakeRequestTask inner class as static as this can be a memory leak.
For your question, You need to pass the context to the class like below:
private static class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
private WeakReference<Context> weakReference;
MakeRequestTask(Context context) {
//Some stuff
weakReference = new WeakReference<>(context);
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
// swipe layout will not be shown if fragment is not visible or destroyed
if (isFragmentVisible) {
swipeRefreshLayout.setRefreshing(false);
}
// toast will be shown no matter what fragment is visible
if (weakReference != null) {
Context context = weakReference.get();
if (context != null) {
Toast.makeText(context, "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
}
}
#Override
protected void onCancelled() {
if (isFragmentVisible) {
swipeRefreshLayout.setRefreshing(false);
}
//Some stuff
}
}
Try this way
Declare a boolean in Fragment_A
private boolean isFragmentVisible=false;
In Fragment_A class
Make this boolean true in onCreateView() of this Fragment_A
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.lyourlayout, container, false);
isFragmentVisible = true;
return view;
}
And make this boolean false in onDestroyView() of this fragment A
#Override
public void onDestroyView() {
super.onDestroyView();
isFragmentVisible = false;
}
Finally use it in Asyntask of Fragment_A like this
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
MakeRequestTask() {
//Some stuff
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
// swipe layout will not be shown if fragment is not visible or destroyed
if(isFragmentVisible){
swipeRefreshLayout.setRefreshing(false);
}
// toast will be shown no matter what fragment is visible
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
#Override
protected void onCancelled() {
if(isFragmentVisible){
swipeRefreshLayout.setRefreshing(false);
}
//Some stuff
}
}
Or you can just use and interface or an EventBus in the onPostExecute method, and show the Toast inside the activity.
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
activityContractInterface.showToast()
}
}
And in your Activity:
#Override
public void showToast(){
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
Or the EventBus approach:
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
EventBus.getDefault().post(new ShowToastEvent())
//just create an empty class, hope you know what EventBus is
}
And in your activity:
#Subscribe(threadMode = ThreadMode.Main){
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
EventBusLibrary
I try to understand basic design pattern behind implementing AsyncTask and attaching it to Fragment. I follow numerous tutorials and implemented code (which gives nothing):
1) my Fragment class which contains AsyncTask:
public class MyFragment extends Fragment
{
public interface TaskCallback
{
void onPreExecute();
void onProgressUpdate(Integer... i);
void onPostExecute();
}
private TaskCallback mCallback;
private Task mTask;
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
mCallback = (TaskCallback) activity;
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRetainInstance(true);
mTask = new Task();
mTask.execute();
}
private class Task extends AsyncTask<Void, Integer, Void>
{
#Override
protected void onPreExecute()
{
mCallback.onPreExecute();
}
#Override
protected Void doInBackground(Void... params)
{
for (int i=0; i<100; i++)
{
publishProgress(i*10);
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values)
{
mCallback.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Void aVoid)
{
mCallback.onPostExecute();
}
}
}
2) my Main Activity code
public class MainActivity extends ActionBarActivity implements MyFragment.TaskCallback
{
private ProgressBar mProgress;
private MyFragment mTaskFragment;
private final static String TAG_FRAGMENT = "Fragment Task";
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProgress = (ProgressBar) findViewById(R.id.progressBar1);
mProgress.setProgress(0);
//create fragment manager and fragment instance
FragmentManager mFM = getSupportFragmentManager();
mTaskFragment = (MyFragment) mFM.findFragmentByTag(TAG_FRAGMENT);
if(mFM == null)
{
mTaskFragment = new MyFragment();
mFM.beginTransaction().add(mTaskFragment, TAG_FRAGMENT).commit();
}
//I added this method, but to no help, the same without this method
mFM.executePendingTransactions();
}
#Override
public void onPreExecute()
{
mProgress.setVisibility(View.VISIBLE);
}
#Override
public void onProgressUpdate(Integer... i)
{
mProgress.setProgress(i[0]);
}
#Override
public void onPostExecute()
{
mProgress.setVisibility(View.INVISIBLE);
}
}
Basically, when run the code by Debug, after
mTaskFragment = (MyFragment) mFM.findFragmentByTag(TAG_FRAGMENT);
if(mFM == null)
{
mTaskFragment = new MyFragment();
mFM.beginTransaction().add(mTaskFragment, TAG_FRAGMENT).commit();
}
Gives mTaskFragment = null
I suppose here is the problem (mTaskFragment = null), the code doesn't create instance of MyFragment.
The question: how should I change the code to update ProgressBar from background by using this (Fragment + AsyncTask) pattern?
another question: Fragment onAttached(Activity a) is deprecated, now we should use onAttached(Context context), does it mean it should be implemented like: mCallback = (TaskCallback) context;?
Replace if(mFM == null) with if(mTaskFragment == null) you want to check if the fragment is null not the FragmentManager.
Also the updated method for:
public void onAttach(Activity activity) is public void onAttach(Context context)
Using: mCallback = (TaskCallback) context; is fine as Context is a superclass of Activty (just make sure your Activity is implementing the interface)
I read this http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html. And I played the example code in the link. To my surprise, fm.findFragmentByTag(TAG_TASK_FRAGMENT) does not return null when I rotate the phone, if I remove setRetainInstance(true) in the TaskFragment.onCreate(). I copied the code here with one line change (remove setRetainInstance(true)).
Please explain why fm.findFragmentByTag(TAG_TASK_FRAGMENT) does not return null in this case.
public class MainActivity extends Activity implements TaskFragment.TaskCallbacks {
private static final String TAG_TASK_FRAGMENT = "task_fragment";
private TaskFragment mTaskFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
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
public void onPreExecute() { }
#Override
public void onProgressUpdate(int percent) { }
#Override
public void onCancelled() { }
#Override
public void onPostExecute() { }
}
public class TaskFragment extends Fragment {
interface TaskCallbacks {
void onPreExecute();
void onProgressUpdate(int percent);
void onCancelled();
void onPostExecute();
}
private TaskCallbacks mCallbacks;
private DummyTask mTask;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (TaskCallbacks) activity;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// I remove this call to produce the problem
// setRetainInstance(true);
mTask = new DummyTask();
mTask.execute();
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
private class DummyTask extends AsyncTask<Void, Integer, Void> {
#Override
protected void onPreExecute() {
if (mCallbacks != null) {
mCallbacks.onPreExecute();
}
}
#Override
protected Void doInBackground(Void... ignore) {
for (int i = 0; !isCancelled() && i < 100; i++) {
SystemClock.sleep(100);
publishProgress(i);
}
return null;
}
#Override
protected void onProgressUpdate(Integer... percent) {
if (mCallbacks != null) {
mCallbacks.onProgressUpdate(percent[0]);
}
}
#Override
protected void onCancelled() {
if (mCallbacks != null) {
mCallbacks.onCancelled();
}
}
#Override
protected void onPostExecute(Void ignore) {
if (mCallbacks != null) {
mCallbacks.onPostExecute();
}
}
}
}
SetRetainInstance controls whether the entire fragment (and its contents) is retained in memory or whether it is recreated as a new Fragment from its Bundle.
The only time it would return null is the very first time the app is run. After that it has been added to the FragmentManager and is always available. (Rotating the device does not clear the FragmentManager regardless of whether you use SetRetainInstance or not)
You seem to think that SetRetainInstance controls whether the fragment is kept in the FragmentManager or not. It does not.
In your example, the AsyncTask starts running the first time the Fragment is created. SetRetainInstance is used to stop the OnDestroy method of the Fragment being called. After an orientation change, the fragment and its running task is still in the FragmentManager and the task is still running. Without SetRetainInstance, when the Orientation change occurs, the fragment is destroyed and recreated from its bundle when you retrieve it from the FragmentManager. This puts the AsyncTask in a delicate state as the task could still be running even if its hosting Fragment has been destroyed possibly leading to a crash.
See this question for a more in depth explanation.
Understanding Fragment's setRetainInstance(boolean)
On my main activity I have a Fragment in which I apply setRetainInstance(true) so that the AsyncTask I use into it is not disturbed by orientation change.
A lot of work is processed by the AsyncTask. That's why I would like to display a dialog with a progressBar on top of my activity.
I made some researches and I succeed in doing with a DialogFragment:
public class DialogWait extends DialogFragment {
private ProgressBar progressBar;
public DialogWait() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_wait, container);
Dialog dialog = getDialog();
dialog.setTitle("Hello");
setCancelable(false);
progressBar = (ProgressBar) view.findViewById(R.id.progress);
return view;
}
public void updateProgress(int value) {
progressBar.setProgress(value);
}
And here is my AsyncTask:
public class InitAsyncTask extends AsyncTask<Void, Integer, Void> {
private Context activity;
private OnTaskDoneListener mCallback;
private DialogWait dialog;
public InitAsyncTask(Context context, OnTaskDoneListener callback, DialogWait dialogWait) {
activity = context;
mCallback = callback;
dialog = dialogWait;
}
#Override
protected Void doInBackground(Void... params) {
doStuff();
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
dialog.updateProgress(values[0]);
}
#Override
protected void onPostExecute(Void result) {
publishProgress(100);
if(dialog != null)
dialog.dismiss();
mCallback.onTaskDone();
}
private void doStuff() {
//...
}
}
If I don't change the screen rotation, it works fine. But if I do, the dialog is dismissed and a few seconds later, I got a NullPointerEsception which nonsense since I set the condition: if(dialog != null)
What am I doing wrong?
Solution found!
I was not doing the right thing with the Fragment containing my AsyncTask.
Because, I haven't really understood the concept of orientation in Fragment, I get it thanks to this link: http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
Override onCreate, and onDestroyView methods in your DialogWait as follows:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) {
getDialog().setDismissMessage(null);
}
super.onDestroyView();
}
The project I'm working on is slightly more complicated but I made this simple test to try to track down what was wrong with my code. The progress dialog never dismisses. I had it at one point where they weren't returning null. '
public class SyncTestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new mTask(this).execute();
}
public class mTask extends AsyncTask<Void, Void, Void> {
Context mContext;
ProgressDialog progressDialog;
public mTask(Context aContext) {
mContext = aContext;
}
#Override
public void onPreExecute() {
progressDialog = new ProgressDialog(mContext);
progressDialog.setMessage("New...");
progressDialog.show();
}
#Override
public Void doInBackground(Void... params) {
return null;
}
public Void onPostExecute(Void... params) {
progressDialog.dismiss();
return null;
}
}
}
The parameters are wrong, use this:
#Override
protected void onPostExecute(Void result) {
progressDialog.dismiss();
return;
}
I am agree with Cesar and Shailendra answers, but still let me make little improvement over it:
#Override
protected void onPostExecute(Void result) {
if(progressDialog.isShowing())
{
progressDialog.dismiss();
}
return;
}
Missing #Override notation before onPostExecute. Also return null is not required.