I'm using a retained DialogFragment which display a progression dialog during a TCP connection executed in an AsyncTask. The AsyncTask is executed in the fragmebt's onStart() method, and cancelled either when the DialogFragment is cancelled, or in the fragment's onStop() method.
When my task finished (i.e, tcp connection is established or failed), or is cancelled, I dismiss the dialog using the dismiss() method.
The problem is when I press "Home" when the AsyncTask is running. In this case the fragment onStop() method is called, which cancels the AsyncTask, and try to dismiss the dialog, but then I get the following error :
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
I saw there is a method named dismissAllowingStateLoss() which will avoid this exception but I'm not sure it's the best solution, I don't want it to "hide" an issue.
The expected behavior is the following: if the user leaves the activity, I want to cancel the connection, that's why I stop it in onStop().
Here is the Fragment code for reference :
public class TcpConnectionFragment extends DialogFragment {
private static final Logger logger = LoggerFactory.getLogger(TcpConnectionFragment.class);
private TcpConnectionTask connectionTask;
private String host;
private int port;
public static TcpConnectionFragment newInstance(String host, int port) {
Bundle bundle = new Bundle();
bundle.putString("ADDRESS", host);
bundle.putInt("PORT", port);
TcpConnectionFragment fragment = new TcpConnectionFragment();
fragment.setArguments(bundle);
return fragment;
}
private void startAsyncTask() {
logger.info("Starting asynchronous connection task");
connectionTask = new TcpConnectionTask(this, host, port);
connectionTask.execute();
}
private void stopAsyncTask() {
if (connectionTask == null)
return;
logger.info("Stopping asynchronous connection task");
connectionTask.cancel(false);
try {
SocketChannel channel = connectionTask.getSocketChannel();
if (channel != null)
channel.close();
} catch (IOException ignored) {
// We don't care about this error here, we can't recover.
}
}
public TcpConnectionFragment() {
super();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (getArguments() == null)
throw new IllegalArgumentException("Missing arguments");
host = getArguments().getString("ADDRESS");
port = getArguments().getInt("PORT");
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new MaterialDialog.Builder(getContext())
.title(R.string.tcp_connect_dialog_title)
.content(R.string.tcp_connect_dialog_text, host, port)
.progress(true, 0)
.build();
}
#Override
public void onStart() {
super.onStart();
startAsyncTask();
}
#Override
public void onStop() {
super.onStop();
stopAsyncTask();
}
#Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
logger.info("Cancelled dialog.");
stopAsyncTask();
}
/**
* This override is required to avoid a bug in the support library.
*/
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
/**
* Called by the {#link TcpConnectionTask} once connected.
*
* #param transport The transport created during the conenction.
*/
void onConnected(Transport transport) {
connectionTask = null;
dismiss();
}
/**
* Called by the {#link TcpConnectionTask} once the connection is cancelled.
*/
void onConnectionCancelled() {
connectionTask = null;
dismiss();
}
/*
* Called by the {#link TcpConnectionTask} if an error occurs during the connection.
*/
void onConnectionError(Exception error) {
connectionTask = null;
dismiss();
if (getParentFragment() == null || getParentFragment().getView() == null)
return;
Snackbar.make(getParentFragment().getView().findViewById(R.id.frame_layout),
"Error: " + error.getMessage(), Snackbar.LENGTH_LONG).show();
}
}
Try to call dismiss from onPause() instead. That should do it.
Check out the DialogFragment diagram from this thread:
In a DialogFragment, what should onCreate do?
Example on cancellation callback:
mCancelObj = new CancellationObject();
new AsyncTask<Void, Void, Void>() {
// do something
// whenFinished check if !mCancelObj.isCancelled -> dialog.dismiss();
}.execute();
void onPause(){
mCancelObj.cancel();
}
Related
I've got an Activity that implements a retained fragment to perform a time consuming task. This allows me to rotate the screen without loosing a reference to my async task and without stopping it from being executed. I have a button on the activity to launch the Async task. Everytime the activity is created, for example due to a rotated screen, the activity checks if its retained fragment has an async task running. If so, it shows the progressDialog to let the user know there are still some tasks running on the background. I attach the code that does what I mentioned. Actually it works. However, what is strange is that it works everytime but the first! When I push the button to launch the Async process for the first time, the progressDialog is not shown, eventhough "onPreExecute()" is called and the line "progressDialog.show()" executed. If I rotate the screen after I push the button the progressDialog shows and when the process ends, If I push the button again then it works just fine. As I said, it is working all the time except the first one. Any idea why?
Thanks!
Activity
public class Activity extends AppCompatActivity implements ActivityTaskFragment.TaskCallbacks{
private ProgressDialog progressDialog;
private static final String TAG_TASK_FRAGMENT = "task_fragment";
private ActivityTaskFragment activityTaskFragment;
//Some additional code here
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crear_turno);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//------------------------------------------------------
setListeners();
setFragment();
createProgressDialog();
showProgressDialog();
}
private void showProgressDialog()
{
if(activityTaskFragment!=null)
{
if(activityTaskFragment.isRunning())
{
progressDialog.show();
}
}
}
private void setFragment()
{
FragmentManager fm = getFragmentManager();
activityTaskFragment = (ActivityTaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT);
// If the Fragment is non-null, then it is currently being
// retained across a configuration change.
if (activityTaskFragment == null) {
activityTaskFragment = new ActivityTaskFragment();
fm.beginTransaction().add(activityTaskFragment, TAG_TASK_FRAGMENT).commit();
}
}
private void setListeners() {
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
activityTaskFragment.execute();
}
});
}
//fragment interface implementation
#Override
public void onPreExecute()
{
if(progressDialog!=null)
progressDialog.show();
}
#Override
public void onCancelled() {
if(progressDialog!=null)
{
if(progressDialog.isShowing())
{
progressDialog.dismiss();
}
}
}
#Override
public void onPostExecute()
{
if(progressDialog!=null)
{
if(progressDialog.isShowing())
{
progressDialog.dismiss();
}
}
}
private void createProgressDialog()
{
if(progressDialog == null)
{
progressDialog = new ProgressDialog(Activity.this);
progressDialog.setTitle("Executing job");
progressDialog.setMessage("please wait...");
}
}
}
TaskFragment
/**
* This Fragment manages a single background task and retains
* itself across configuration changes.
*/
public class ActivityTaskFragment extends Fragment {
/**
* Callback interface through which the fragment will report the
* task's progress and results back to the Activity.
*/
interface TaskCallbacks {
void onPreExecute();
void onCancelled();
void onPostExecute();
}
private TaskCallbacks mCallbacks;
private CheckTask mTask;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (TaskCallbacks) activity;
}
/**
* Hold a reference to the parent Activity so we can report the
* task's current progress and results. The Android framework
* will pass us a reference to the newly created Activity after
* each configuration change.
*/
public void execute()
{
mTask.cancel(true);
mTask = new CheckTask();
mTask.execute();
}
public void cancel()
{
if(mTask != null)
{
mTask.cancel(true);
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if(context instanceof Activity) {
Activity activity = (Activity) context;
mCallbacks = (TaskCallbacks) activity;
}
}
/**
* This method will only be called once when the retained
* Fragment is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
mTask = new CheckTask();
}
/**
* Set the callback to null so we don't accidentally leak the
* Activity instance.
*/
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
/**
* A dummy task that performs some (dumb) background work and
* proxies progress updates and results back to the Activity.
*
* Note that we need to check if the callbacks are null in each
* method in case they are invoked after the Activity's and
* Fragment's onDestroy() method have been called.
*/
private class CheckTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute()
{
if (mCallbacks != null) {
mCallbacks.onPreExecute();
}
}
/**
* Note that we do NOT call the callback object's methods
* directly from the background thread, as this could result
* in a race condition.
*/
#Override
protected Void doInBackground(Void... ignore) {
/*
LONG TIME CONSUMING TASK
*/
return null;
}
#Override
protected void onCancelled() {
running = false;
if (mCallbacks != null) {
mCallbacks.onCancelled();
}
}
#Override
protected void onPostExecute(Void ignore)
{
running = false;
if (mCallbacks != null) {
mCallbacks.onPostExecute();
}
}
}
public boolean isRunning()
{
if(mTask!=null)
{
if(mTask.getStatus() == AsyncTask.Status.RUNNING)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
}
I discovered a strange behaviour today.
I have my activity which connects to the GoogleApiClient in onStart() and disconnects in the onStop()
The activity uses a GridViewPager to show my fragments. To send messages through the Data Layer i use a callback interface between activity and fragment.
If i call sendMessage() from a button within the Activity layout it works fine. If sendMessage() is executed by the fragment using the callback interface sendMessage() shows the "not connected" Toast.
In both ways the same method in the Activity is called so how is it possible that it behaves different ?
I should mention that the problem only occours after the application is restarted for the first time.
Activity
public class WearPlex extends WearableActivity implements
NavigationRemoteCallbacks,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private List<Node> nodeList = new ArrayList<Node>();
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private GoogleApiClient googleApiClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wear_plex);
setAmbientEnabled();
fragmentList.add(NavigationRemoteFragment.getInstance(this));
GridViewPager mGridPager = (GridViewPager)findViewById(R.id.gridViewPager);
mGridPager.setAdapter(new MainGridPageAdapter(getFragmentManager(), fragmentList));
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
#Override
protected void onStart() {
super.onStart();
googleApiClient.connect();
}
#Override
protected void onStop() {
googleApiClient.disconnect();
super.onStop();
}
#Override
public void onConnected(Bundle bundle) {
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
nodeList.clear();
Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) nodeList.add(node);
}
});
}
#Override
public void navigationRemoteSendCommand(String commandPath) {
sendMessage(commandPath, null);
}
public void debugOnClick(View view) {
sendMessage("/debug", null);
}
public void sendMessage(String path, byte[] data) {
if (googleApiClient.isConnected()) {
for (int i = 0; i < nodeList.size(); i++) {
if (nodeList.get(i).isNearby()) {
Toast.makeText(this, "Send message", Toast.LENGTH_SHORT).show();
Wearable.MessageApi.sendMessage(googleApiClient, nodeList.get(i).getId(), path, data);
}
}
} else {
Toast.makeText(this, "Not connected", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(this, "Connection failed", Toast.LENGTH_SHORT).show();
}
Fragment
public class NavigationRemoteFragment extends Fragment {
private static NavigationRemoteFragment navigationRemoteFragment = null;
private NavigationRemoteCallbacks callbackHandler = null;
private ImageButton navBtnCenter;
public static NavigationRemoteFragment getInstance(NavigationRemoteCallbacks handler) {
if (navigationRemoteFragment == null) {
navigationRemoteFragment = new NavigationRemoteFragment();
navigationRemoteFragment.callbackHandler = handler;
}
return navigationRemoteFragment;
}
public NavigationRemoteFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_navigation_remote, container, false);
navBtnCenter = (ImageButton)v.findViewById(R.id.navBtnCenter);
navBtnCenter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbackHandler.navigationRemoteSendCommand("/debug");
}
});
return v;
}
}
Callback interface
public interface NavigationRemoteCallbacks {
public void navigationRemoteSendCommand(String commandPath);
}
EDIT 1 code for MainGridPageAdapter
public class MainGridPageAdapter extends FragmentGridPagerAdapter {
private List<Fragment> fragmentList = null;
public MainGridPageAdapter(FragmentManager fm, List<Fragment> fragmentList) {
super(fm);
this.fragmentList = fragmentList;
}
#Override
public Fragment getFragment(int i, int i1) {
if (i1 < fragmentList.size()) return fragmentList.get(i1);
return null;
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount(int i) {
return fragmentList.size();
}
You don't show the code for MainGridPageAdapter so I don't know how it is managing fragments. You mention that the problem occurs after a restart. Looking at the code in WearPlex.onCreate(), I suspect that the problem is caused fragments that are holding a reference to an old, destroyed instance of the activity.
A poorly documented behavior of FragmentManager is that it saves its state across restarts. This is often overlooked, resulting in duplicate fragment instances after a restart. The correct pattern for managing fragment creation in the onCreate() method of the host activity is:
if (savedInstanceState == null) {
// Not a restart
// Create a new instance of the fragment
// Add it to the fragment manager
} else {
// Restart
// The fragment manager has saved and restored the fragment instances
// Use findFragmentById() to get the fragment if you need it
}
You are not using savedInstanceState in onCreate() to test for restart. Are you seeing more fragments than you expect after restart? If so, the original fragments are holding a reference to the old activity, which was stopped, and has a disconnected GoogleApiClient. If the NavBtn of one of those fragments is clicked, you will see the "not connected" toast.
Update
The problem is caused by the way you are creating new instances of NavigationRemoteFragment, specifically the use of static member navigationRemoteFragment. After a restart, when the activity is recreated, the code calls NavigationRemoteFragment.getInstance(this). getInstance() finds navigationRemoteFragment not null because it is static, and does not create a new fragment. The fragment returned is the old one, which holds a reference to the old activity, which has been stopped and has a disconnected GoogleApiClient.
This could be confirmed by using the isDestroyed method and adding a some debug logging:
#Override
public void navigationRemoteSendCommand(String commandPath) {
if (isDestroyed()) {
Log.w("TEST", "This is an old instance of the activity");
}
sendMessage(commandPath, null);
}
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)
Hi i have a function to get users from website database
my function
private void get_users() {
try {
url = "my address";
dbGetData3 = new DbGetData();
new Thread(new Runnable() {
public void run() {
data = dbGetData3.getDataFromDB(url);
runOnUiThread(new Runnable() {
#Override
public void run() {
userha = parseJSON3(data);
}
});
}
}).start();
Toast.makeText(context, "please wait ", Toast.LENGTH_LONG)
.show();
} catch (Exception e) {
toast(9);
}
Now i want add a loading progress bar while fetch data finished.
I use AsyncTask like this:
private class LongOperation extends AsyncTask<String, Void, String> {
protected void onPreExecute() {
progressDialog = new ProgressDialog(Login.this);
progressDialog.setTitle("Processing...");
progressDialog.setMessage("Please wait...");
progressDialog.setCancelable(true);
progressDialog.show();
}
protected String doInBackground(String... params) {
try {
get_users();
} catch (Exception e) {
}
return null;
}
protected void onPostExecute(String result) {
progressDialog.dismiss();
}
}
and i use this code for excute
mytask = new LongOperation();
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
mytask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
mytask.execute();
imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
onCreate(savedInstanceState);
}
});
but progress dialog dose not show for me (get user worked)
i change my code like this:
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
mytask.onPreExecute();
mytask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
else
{
mytask.onPreExecute();
mytask.execute();
}
then my progress dialog allways show
i test other code in stackoverflow like
AsyncTask doInBackground does not run
AsyncTask called from Handler will not execute doInBackground
Android SDK AsyncTask doInBackground not running (subclass)
but that not work for me
please help me tankyou
Consdier using a LoaderManager and an AsyncTaskLoader for this sort of stuff.
AsyncTasks are a pain in the ass as because you have to manage their lifecycle with screen-rotations etc. With a LoaderManager all of that is in the past.
Below is an example of a loader which loads a list of "items".
public class ItemsLoader extends AsyncTaskLoader<List<Item>> {
private static final String TAG = "ItemsLoader";
private List<Item> mItems;
private ItemUpdatedReceiver mObserver;
private int mSomeParam;
public static class ItemUpdatedReceiver extends BroadcastReceiver {
private static final String TAG = "ItemLoader";
final ItemsLoader mLoader;
public ItemUpdatedReceiver(ItemsLoader mLoader) {
this.mLoader = mLoader;
// listen for changes to the account we're using
IntentFilter filter = new IntentFilter(GlobalConstants.ACTION_ITEMS_UPDATED);
LocalBroadcastManager.getInstance(mLoader.getContext()).registerReceiver(this, filter);
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (GlobalConstants.ACTION_ITEMS_UPDATED.equals(action)) {
mLoader.onContentChanged();
}
}
}
public void setSomeParam(int someParam){
mSomeParam = someParam;
onContentChanged();
}
public ItemsLoader(Context context, int someParam) {
super(context);
mSomeParam = someParam;
onContentChanged();
}
#Override
public List<Item> loadInBackground() {
// do whatever you need to do here
ArrayList<Item> Items = new ArrayList<>();
return Items;
}
/**
* Called when there is new data to deliever to the client.
*
* #param data
*/
#Override
public void deliverResult(List<Item> data) {
if (isReset()) {
// an async query came in while the loader is stopped, we don't need the result
//release resources if needed
onReleaseResources(data);
}
List<Item> oldItems = mItems;
mItems = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(mItems);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldItems != null) {
onReleaseResources(oldItems);
}
}
#Override
protected void onStartLoading() {
super.onStartLoading();
if (mItems != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mItems);
}
// start listening for changes
if (mObserver == null) {
mObserver = new ItemUpdatedReceiver(this);
}
if (takeContentChanged() || mItems == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
#Override
public void onCanceled(List<Item> items) {
super.onCanceled(items);
// At this point we can release the resources associated with 'profile'
// if needed.
onReleaseResources(items);
}
#Override
protected void onReset() {
super.onReset();
// Ensure the laoder is stopped
onStopLoading();
// At this point we can release the resources if needed.
if (mItems != null) {
onReleaseResources(mItems);
mItems = null;
}
// Stop monitoring for changes.
if (mObserver != null) {
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mObserver);
mObserver = null;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
private void onReleaseResources(List<Item> data) {
// For a simple List<> there is nothing to do. For something
// like a Cursor, we would close it here.
}
}
To use this class, in your activity you must extend LoaderManager.LoaderCallbacks> and override the methods:
public Loader<List<Item>> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// start the loading dialog here
return new ItemsLoader(context);
}
public void onLoadFinished(Loader<List<Item>> loader, List<Item>data) {
// do something with your data, hide the progress dialog
}
public void onLoaderReset(Loader<Cursor> loader) {
// set the old data to null
}
To actually start loading:
getLoaderManager().initLoader(LOADER_ID, null, this);
I am trying to implement this tutorial, for handling Configuration changes while running background tasks. Everything works fine, and the app does not crash after a configuration change. In the tutorial, a progress bar is used to display progress. But in my own implementation i want to use a Progress Dialog.
I have used progress Dialog's lots of times, so calling it and getting to appear is not the problem. My problem is that unlike the progress Bar, the progress dialog gets dismissed on configuration change. Just like that.
Here is my code:
My MainActivity:
private TaskFragment mTaskFragment;
private ProgressDialog mProgressDialog;
private TextView mPercent;
private Button mButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate(Bundle)");
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initialize views
mButton = (Button) findViewById(R.id.task_button);
mButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (mTaskFragment.isRunning()) {
mTaskFragment.cancel();
} else {
mTaskFragment.start();
}
}
});
mProgressBar = new ProgressDialog(this);
FragmentManager fm = getSupportFragmentManager();
mTaskFragment = (TaskFragment) fm.findFragmentByTag("task");
// If the Fragment is non-null, then it is currently being
// retained across a configuration change.
if (mTaskFragment == null) {
mTaskFragment = new TaskFragment();
fm.beginTransaction().add(mTaskFragment, "task").commit();
}
if (mTaskFragment.isRunning()) {
mButton.setText(getString(R.string.cancel));
} else {
mButton.setText(getString(R.string.start));
}
}
/****************************/
/***** CALLBACK METHODS *****/
/****************************/
#Override
public void onPreExecute() {
Log.i(TAG, "onPreExecute()");
mProgressBar.setTitle("Wacky");
mProgressBar.setMessage("wack");
mProgressBar.setIndeterminate(true);
mProgressBar.show();
mButton.setText(getString(R.string.cancel));
mButton.setText(getString(R.string.cancel));
Toast.makeText(this, R.string.task_started_msg, Toast.LENGTH_SHORT).show();
}
#Override
public void onProgressUpdate(int percent) {
//Log.i(TAG, "onProgressUpdate(" + percent + "%)");
}
#Override
public void onCancelled() {
Log.i(TAG, "onCancelled()");
mButton.setText(getString(R.string.start));
Toast.makeText(this, R.string.task_cancelled_msg, Toast.LENGTH_SHORT).show();
}
#Override
public void onPostExecute() {
Log.i(TAG, "onPostExecute()");
mButton.setText(getString(R.string.start));
Toast.makeText(this, R.string.task_complete_msg, Toast.LENGTH_SHORT).show();
}
My headless Fragment that holds my asyncTask
/**
* This Fragment manages a single background task and retains itself across
* configuration changes.
*/
public class TaskFragment extends Fragment {
public static final String TAG = TaskFragment.class.getSimpleName();
/**
* Callback interface through which the fragment can report the task's
* progress and results back to the Activity.
*/
public static interface TaskCallbacks {
public void onPreExecute();
public void onProgressUpdate(int percent);
public void onCancelled();
public void onPostExecute();
}
public TaskCallbacks mCallbacks;
public DummyTask mTask;
public boolean mRunning;
/**
* Android passes us a reference to the newly created Activity by calling this
* method after each configuration change.
*/
#Override
public void onAttach(Activity activity) {
Log.i(TAG, "onAttach(Activity)");
super.onAttach(activity);
if (!(activity instanceof TaskCallbacks)) {
throw new IllegalStateException("Activity must implement the TaskCallbacks interface.");
}
// Hold a reference to the parent Activity so we can report back the task's
// current progress and results.
mCallbacks = (TaskCallbacks) activity;
}
/**
* This method is called only once when the Fragment is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate(Bundle)");
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
/**
* This method is <em>not</em> called when the Fragment is being retained
* across Activity instances.
*/
#Override
public void onDestroy() {
Log.i(TAG, "onDestroy()");
super.onDestroy();
cancel();
}
/*****************************/
/***** TASK FRAGMENT API *****/
/*****************************/
/**
* Start the background task.
*/
public void start() {
if (!mRunning) {
mTask = new DummyTask(this, mCallbacks);
mTask.execute();
mRunning = true;
}
}
/**
* Cancel the background task.
*/
public void cancel() {
if (mRunning) {
mTask.cancel(false);
mTask = null;
mRunning = false;
}
}
/**
* Returns the current state of the background task.
*/
public boolean isRunning() {
return mRunning;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.i(TAG, "onActivityCreated(Bundle)");
super.onActivityCreated(savedInstanceState);
}
}
My Background Task (in a seperate outer class)
/**
* A dummy task that performs some (dumb) background work and proxies progress
* updates and results back to the Activity.
*/
public class DummyTask extends AsyncTask<Void, Integer, Void> {
private TaskFragment fragment;
private TaskCallbacks callbacks;
private ProgressDialog mProgressBar;
MainActivity activity;
public DummyTask(TaskFragment taskFragment, TaskCallbacks mCallbacks) {
// TODO Auto-generated constructor stub
this.fragment = taskFragment;
this.callbacks = mCallbacks;
}
#Override
protected void onPreExecute() {
// Proxy the call to the Activity
fragment.mCallbacks.onPreExecute();
fragment.mRunning = true;
}
#Override
protected Void doInBackground(Void... ignore) {
for (int i = 0; !isCancelled() && i < 100; i++) {
//Log.i(TAG, "publishProgress(" + i + "%)");
SystemClock.sleep(100);
publishProgress(i);
}
return null;
}
#Override
protected void onProgressUpdate(Integer... percent) {
// Proxy the call to the Activity
fragment.mCallbacks.onProgressUpdate(percent[0]);
}
#Override
protected void onCancelled() {
// Proxy the call to the Activity
fragment.mCallbacks.onCancelled();
fragment.mRunning = false;
}
#Override
protected void onPostExecute(Void ignore) {
// Proxy the call to the Activity
fragment.mCallbacks.onPostExecute();
fragment.mRunning = false;
}
}
I am thinking it is the context which i am passing the progress dialog in the onCreate method of my Main Activity. Thanks for your help.
First, Activity is subclass of Context, you should know this already. Second, if Activity is destroyed, it is don't have a window anymore. Third, Dialog uses Context (read Activity) not because it wants so, but because it uses window associated with Activity to display itself.
It should be perfectly understandable, why after destroying activity during configuration change, Dialog no longer visible to you.
Method of preserving objects you using is good, but it can't preserve anything that would be destroyed during configuration change, such as any object that related to non-application Context, all this objects you need create manually every time Context changes.
You should use onSaveInstanceState(Bundle) to store state of ProgressDialog (shown or not) and show it again in your onCreate(Bunde) using value stored in Bundle.
Just an example
#Override
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putBoolean("SHOW_DIALOG", mProgressBar.isShowing());
}
//...
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
if (savedInstanceState != null){
if (savedInstanceState.getBoolean("SHOW_DIALOG") && mTaskFragment.isRunning()){
mProgressBar.setTitle("Wacky");
mProgressBar.setMessage("wack");
mProgressBar.setIndeterminate(true);
mProgressBar.show();
}
}
//...