Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fragment MyFragment not attached to Activity

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?

like image 675
nhaarman Avatar asked Jun 06 '12 17:06

nhaarman


People also ask

Which method is called when a fragment is not connected to the activity?

The onActivityCreated() method is called after onCreateView() and before onViewStateRestored() . onDestroyView() : Called when the View previously created by onCreateView() has been detached from the Fragment . This call can occur if the host Activity has stopped, or the Activity has removed the Fragment .

How do I attach a fragment to an activity?

Add a fragment to an activity You can add your fragment to the activity's view hierarchy either by defining the fragment in your activity's layout file or by defining a fragment container in your activity's layout file and then programmatically adding the fragment from within your activity.

What is called when the fragment is detached from the activity?

The onDetach() callback is invoked when the fragment has been removed from a FragmentManager and is detached from its host activity. The fragment is no longer active and can no longer be retrieved using findFragmentById() . onDetach() is always called after any Lifecycle state changes.


2 Answers

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.

like image 192
nhaarman Avatar answered Sep 28 '22 20:09

nhaarman


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(...) 
like image 29
Bitcoin Cash - ADA enthusiast Avatar answered Sep 28 '22 19:09

Bitcoin Cash - ADA enthusiast