Fragment MyFragment not attached to Activity

AndroidAndroid FragmentsActionbarsherlock

Android Problem Overview


I've created a small test app which represents my problem. I'm using ActionBarSherlock to implement tabs with (Sherlock)Fragments.

My code: TestActivity.java

public class TestActivity extends SherlockFragmentActivity {
	private ActionBar actionBar;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setupTabs(savedInstanceState);
	}

	private void setupTabs(Bundle savedInstanceState) {
		actionBar = getSupportActionBar();
		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

		addTab1();
		addTab2();
	}

	private void addTab1() {
		Tab tab1 = actionBar.newTab();
		tab1.setTag("1");
		String tabText = "1";
		tab1.setText(tabText);
		tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));

		actionBar.addTab(tab1);
	}

	private void addTab2() {
		Tab tab1 = actionBar.newTab();
		tab1.setTag("2");
		String tabText = "2";
		tab1.setText(tabText);
		tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));

		actionBar.addTab(tab1);
	}
}

TabListener.java

public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
	private final SherlockFragmentActivity mActivity;
	private final String mTag;
	private final Class<T> mClass;

	public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
		mActivity = activity;
		mTag = tag;
		mClass = clz;
	}

	/* The following are each of the ActionBar.TabListener callbacks */

	public void onTabSelected(Tab tab, FragmentTransaction ft) {
		SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

		// Check if the fragment is already initialized
		if (preInitializedFragment == null) {
			// If not, instantiate and add it to the activity
			SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
			ft.add(android.R.id.content, mFragment, mTag);
		} else {
			ft.attach(preInitializedFragment);
		}
	}

	public void onTabUnselected(Tab tab, FragmentTransaction ft) {
		SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);

		if (preInitializedFragment != null) {
			// Detach the fragment, because another one is being attached
			ft.detach(preInitializedFragment);
		}
	}

	public void onTabReselected(Tab tab, FragmentTransaction ft) {
		// User selected the already selected tab. Usually do nothing.
	}
}

MyFragment.java

public class MyFragment extends SherlockFragment {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		new AsyncTask<Void, Void, Void>() {

			@Override
			protected Void doInBackground(Void... params) {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException ex) {
				}
				return null;
			}
			
			@Override
			protected void onPostExecute(Void result){
				getResources().getString(R.string.app_name);
			}

		}.execute();
	}
}

I've added the Thread.sleep part to simulate downloading data. The code in the onPostExecute is to simulate use of the Fragment.

When I rotate the screen very fast between landscape and portrait, I get an Exception at the onPostExecute code:

> java.lang.IllegalStateException: Fragment MyFragment{410f6060} not > attached to Activity

I think it's because a new MyFragment has been created in the meantime, and was attached to the Activity before the AsyncTask finished. The code in onPostExecute calls upon a unattached MyFragment.

But how can I fix this?

Android Solutions


Solution 1 - Android

I've found the very simple answer: isAdded():

> Return true if the fragment is currently added to its activity.

@Override
protected void onPostExecute(Void result){
    if(isAdded()){
        getResources().getString(R.string.app_name);
    }
}

To avoid onPostExecute from being called when the Fragment is not attached to the Activity is to cancel the AsyncTask when pausing or stopping the Fragment. Then isAdded() would not be necessary anymore. However, it is advisable to keep this check in place.

Solution 2 - Android

The problem is that you are trying to access resources (in this case, strings) using getResources().getString(), which will try to get the resources from the Activity. See this source code of the Fragment class:

 /**
  * Return <code>getActivity().getResources()</code>.
  */
 final public Resources getResources() {
     if (mHost == null) {
         throw new IllegalStateException("Fragment " + this + " not attached to Activity");
     }
     return mHost.getContext().getResources();
 }

mHost is the object that holds your Activity.

Because the Activity might not be attached, your getResources() call will throw an Exception.

The accepted solution IMHO is not the way to go as you are just hiding the problem. The correct way is just to get the resources from somewhere else that is always guaranteed to exist, like the application context:

youApplicationObject.getResources().getString(...)

Solution 3 - Android

I've faced two different scenarios here:

  1. When I want the asynchronous task to finish anyway: imagine my onPostExecute does store data received and then call a listener to update views so, to be more efficient, I want the task to finish anyway so I have the data ready when user cames back. In this case I usually do this:

    @Override protected void onPostExecute(void result) { // do whatever you do to save data if (this.getView() != null) { // update views } }

  2. When I want the asynchronous task only to finish when views can be updated: the case you're proposing here, the task only updates the views, no data storage needed, so it has no clue for the task to finish if views are not longer being showed. I do this:

    @Override protected void onStop() { // notice here that I keep a reference to the task being executed as a class member: if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true); super.onStop(); }

I've found no problem with this, although I also use a (maybe) more complex way that includes launching tasks from the activity instead of the fragments.

Wish this helps someone! :)

Solution 4 - Android

Their are quite trick solution for this and leak of fragment from activity.

So in case of getResource or anything one which is depending on activity context accessing from Fragment it is always check activity status and fragments status as follows

 Activity activity = getActivity(); 
    if(activity != null && isAdded())
 
         getResources().getString(R.string.no_internet_error_msg);
//Or any other depends on activity context to be live like dailog
  

        }
    }

Solution 5 - Android

The problem with your code is the way the you are using the AsyncTask, because when you rotate the screen during your sleep thread:

Thread.sleep(2000) 

the AsyncTask is still working, it is because you didn't cancel the AsyncTask instance properly in onDestroy() before the fragment rebuilds (when you rotate) and when this same AsyncTask instance (after rotate) runs onPostExecute(), this tries to find the resources with getResources() with the old fragment instance(an invalid instance):

getResources().getString(R.string.app_name)

which is equivalent to:

MyFragment.this.getResources().getString(R.string.app_name)

So the final solution is manage the AsyncTask instance (to cancel if this is still working) before the fragment rebuilds when you rotate the screen, and if canceled during the transition, restart the AsyncTask after reconstruction by the aid of a boolean flag:

public class MyFragment extends SherlockFragment {

    private MyAsyncTask myAsyncTask = null;
    private boolean myAsyncTaskIsRunning = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    if(savedInstanceState!=null) {
		    myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning");
	    }
	    if(myAsyncTaskIsRunning) {
		    myAsyncTask = new MyAsyncTask();
		    myAsyncTask.execute();
	    }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
	    super.onSaveInstanceState(outState);
	    outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning);
    }

    @Override
    public void onDestroy() {
	    super.onDestroy();
	    if(myAsyncTask!=null) myAsyncTask.cancel(true);
	    myAsyncTask = null;
		
    }

    public class MyAsyncTask extends AsyncTask<Void, Void, Void>() {

	    public MyAsyncTask(){}
	
	    @Override
	    protected void onPreExecute() {
		    super.onPreExecute();
		    myAsyncTaskIsRunning = true;
	    }
	    @Override
	    protected Void doInBackground(Void... params) {
		    try {
			    Thread.sleep(2000);
		    } catch (InterruptedException ex) {}
		    return null;
	    }

	    @Override
	    protected void onPostExecute(Void result){
		    getResources().getString(R.string.app_name);
		    myAsyncTaskIsRunning = false;
		    myAsyncTask = null;
	    }

    }
}

Solution 6 - Android

if (getActivity() == null) return;

works also in some cases. Just breaks the code execution from it and make sure the app not crash

Solution 7 - Android

I faced the same problem i just add the singletone instance to get resource as referred by Erick

MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);

you can also use

getActivity().getResources().getString(R.string.app_name);

I hope this will help.

Solution 8 - Android

I faced similar issues when the application settings activity with the loaded preferences was visible. If I would change one of the preferences and then make the display content rotate and change the preference again, it would crash with a message that the fragment (my Preferences class) was not attached to an activity.

When debugging it looked like the onCreate() Method of the PreferencesFragment was being called twice when the display content rotated. That was strange enough already. Then I added the isAdded() check outside of the block where it would indicate the crash and it solved the issue.

Here is the code of the listener that updates the preferences summary to show the new entry. It is located in the onCreate() method of my Preferences class which extends the PreferenceFragment class:

public static class Preferences extends PreferenceFragment {
    SharedPreferences.OnSharedPreferenceChangeListener listener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                // check if the fragment has been added to the activity yet (necessary to avoid crashes)
                if (isAdded()) {
                    // for the preferences of type "list" set the summary to be the entry of the selected item
                    if (key.equals(getString(R.string.pref_fileviewer_textsize))) {
                        ListPreference listPref = (ListPreference) findPreference(key);
                        listPref.setSummary("Display file content with a text size of " + listPref.getEntry());
                    } else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) {
                        ListPreference listPref = (ListPreference) findPreference(key);
                        listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once");
                    }
                }
            }
        };
        // ...
    }

I hope this will help others!

Solution 9 - Android

If you extend the Application class and maintain a static 'global' Context object, as follows, then you can use that instead of the activity to load a String resource.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

    @Override
    public void onCreate() {
        super.onCreate();
        GLOBAL_APP_CONTEXT = this;
    }
}

If you use this, you can get away with Toast and resource loading without worrying about lifecycles.

Solution 10 - Android

In my case fragment methods have been called after

getActivity().onBackPressed();

Solution 11 - Android

An old post, but I was surprised about the most up-voted answer.

The proper solution for this should be to cancel the asynctask in onStop (or wherever appropriate in your fragment). This way you don't introduce a memory leak (an asynctask keeping a reference to your destroyed fragment) and you have better control of what is going on in your fragment.

@Override
public void onStop() {
    super.onStop();
    mYourAsyncTask.cancel(true);
}

Solution 12 - Android

I had a similar error message "Fragment MyFragment not attached to Context" in Xamarine Android.

this error messege getting because of this resource calling

button.Text = Resources.GetString(Resource.String.please_wait)

I did fix that by using in Xamarine Android.

if (Context != null && IsAdded){ 
    button.Text = Resources.GetString(Resource.String.please_wait);
}

Solution 13 - Android

simple solution and work 100%

if (getActivity() == null || !isAdded()) return;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnhaarmanView Question on Stackoverflow
Solution 1 - AndroidnhaarmanView Answer on Stackoverflow
Solution 2 - AndroidBitcoin Cash - ADA enthusiastView Answer on Stackoverflow
Solution 3 - AndroidluixalView Answer on Stackoverflow
Solution 4 - AndroidVinayakView Answer on Stackoverflow
Solution 5 - AndroidErick Reátegui DiazView Answer on Stackoverflow
Solution 6 - AndroidsuperUserView Answer on Stackoverflow
Solution 7 - AndroidAristo MichaelView Answer on Stackoverflow
Solution 8 - AndroidMarcellView Answer on Stackoverflow
Solution 9 - AndroidAnthony ChuinardView Answer on Stackoverflow
Solution 10 - AndroidCoolMindView Answer on Stackoverflow
Solution 11 - AndroidRazView Answer on Stackoverflow
Solution 12 - AndroidDilanka FernandoView Answer on Stackoverflow
Solution 13 - AndroidFakhriddin AbdullaevView Answer on Stackoverflow