Fragment MyFragment not attached to Activity
Asked Answered
L

14

440

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?

Louisalouisburg answered 6/6, 2012 at 17:36 Comment(3)
You should use view from fragment inflater. mView = inflater.inflate(R.layout.my_layout, container, false) And now use this view when you want to get resources: mView.getResources().***. It help me to fix this bug.Protero
@Protero That leaks the Context that is attached to your` mView`.Louisalouisburg
May be I don't check it yet. To avoid leak how about to get null mView in onDestroy ?Protero
L
843

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.

Louisalouisburg answered 7/6, 2012 at 23:51 Comment(4)
In my case when I am launching Another application Intent from...then I am getting same error...any suggetion?Bethelbethena
developer.android.com/reference/android/app/… ...there's also isDetached(), that was added on API level 13Rase
When on API <11, you're using developer.android.com/reference/android/support/v4/app/… where it will work.Louisalouisburg
I faced this problem when I used DialogFragment. After dismissing dialogFragment, I tried to start another activity. Then this error occurred. I avoided this error by calling dismiss() after startActivity. The problem was that fragment was already detached from Activity.Economist
P
38

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(...)
Paroxysm answered 30/6, 2016 at 21:0 Comment(2)
I used this solution because I needed to execute getString() when my fragment was paused. ThanksAphis
UPDATE: From the documentation, "After Build.VERSION_CODES#R, Resources must be obtained by Activity or Context. Application#getResources() may report wrong values in multi-window or on secondary displays." You can instead get an instance of Resources with Context.getResources().Fredenburg
S
26

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! :)

Skiles answered 24/4, 2013 at 10:13 Comment(0)
G
24

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


        }
    }
Gwenora answered 2/9, 2016 at 9:44 Comment(3)
isAdded() is enough because: final public boolean isAdded() { return mHost != null && mAdded; }Weary
In my case this checks are not enoug, still getting crashes despite I added these.Centriole
@David, isAdded is enough. I never saw a situation when getString() had crashed if isAdded == true. Are you sure an activity was shown and a fragment was attached?Woolery
G
18

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;
        }

    }
}
Gatekeeper answered 29/5, 2014 at 23:24 Comment(1)
instead if getResources().*** using Fragments.this.getResource().*** helpedIntradermal
R
15
if (getActivity() == null) return;

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

Robertson answered 4/7, 2017 at 16:17 Comment(0)
T
10

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.

Tempura answered 27/8, 2014 at 15:3 Comment(0)
E
2

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!

Elma answered 10/7, 2015 at 21:37 Comment(0)
D
1

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.

Darg answered 8/6, 2015 at 22:39 Comment(2)
I'm being downvoted but no one has explained why. Static contexts are usually bad but I was of the idea that it is not a memory leak if you have a static Application reference.Darg
Your answer is downvoted because this is just a hack not proper solution. Check solution shared by @LouisalouisburgDetrusion
H
1

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);
}
Hollingshead answered 28/1, 2021 at 1:41 Comment(0)
W
0

In my case fragment methods have been called after

getActivity().onBackPressed();
Woolery answered 18/10, 2016 at 12:33 Comment(0)
H
0

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);
}
Hern answered 5/1, 2017 at 11:46 Comment(2)
The most upvoted answer includes this. Also, cancel may not prevent onPostExecute from being invoked.Louisalouisburg
Calling cancel does guarantee onPostExecute will never be called, both calls execute on the same thread hence you are guaranteed it will not be invoked after calling cancelHern
L
0

Add This on your Fragemnt

Activity activity;
@Override
public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    activity = context instanceof Activity ? (Activity) context : null;
}

Then change getContext() , getActivity() , requireActivity() or requireContext() with activity

Luggage answered 27/8, 2022 at 11:36 Comment(0)
C
-1

simple solution and work 100%

if (getActivity() == null || !isAdded()) return;
Circumstantiality answered 25/10, 2020 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.