IllegalStateException on AsyncTask [duplicate] - android

This question already has answers here:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
(17 answers)
Closed 5 years ago.
I'm getting a following error when trying to replace a fragment upon receiving a response from AsyncTask:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
The thing is, I get this error randomly upon restarting my app through Android Studio. In a simplified version my activity contains 4 key methods (onCreate, taskCompleted, parseJSON and fragmentReplace), that determine which fragment should the user see at the start:
private AsyncTask mMyTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyTask = new AsyncTask(this, this);
mMyTask.executeTaskCall("check_user");
}
#Override
public void taskCompleted(String results) {
try {
JSONObject jsonBody = new JSONObject(results);
parseJSON(jsonBody);
}
catch (JSONException je){
}
}
private void parseJSON(JSONObject jsonBody) throws JSONException {
boolean userActive = jsonBody.getBoolean("result");
if (userActive){
fragmentReplace(new FirstFragment(), "FirstFragment");
}
else {
fragmentReplace(new SecondFragment(), "SecondFragment");
}
}
public void fragmentReplace(Fragment fragment, String fragmentTag){
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.layout_container, fragment, fragmentTag)
.commit();
}
What is the reason of this exception happening so random?

You should read WeakReference solution (or may be other also) at java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState.
There is one alternate solution for this problem. Using flag you can handle it, like below
/**
* Flag to avoid "java.lang.IllegalStateException: Can not perform this action after
* onSaveInstanceState". Avoid Fragment transaction until onRestoreInstanceState or onResume
* gets called.
*/
private boolean isOnSaveInstanceStateCalled = false;
#Override
public void onRestoreInstanceState(final Bundle bundle) {
.....
isOnSaveInstanceStateCalled = false;
.....
}
#Override
public void onSaveInstanceState(final Bundle outState) {
.....
isOnSaveInstanceStateCalled = true;
.....
}
#Override
public void onResume() {
super.onResume();
isOnSaveInstanceStateCalled = false;
.....
}
And you can check this boolean value while doing fragment transaction.
private void fragmentReplace(Fragment fragment, String fragmentTag){
if (!isOnSaveInstanceStateCalled) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.layout_container, fragment, fragmentTag)
.commit();
}
}
Update for API 26.1+ (contributed by Stephen M)
Fragment.isStateSaved() has been added since 26.1.0, which can also be used for same purpose.

Related

replay().autoconnect() does not replay events

I'm facing some issues with replay().autoconnect() operator combination.
Before I ask my question, let me share some knowledge I have regarding this operators. I have been following this tutorial on multicasting
What I understood is
replay() operator can re-emit the results of the observable when a new subscriber comes in, without re-executing the source observable (Helps during network calls)
autoconnect() can turn the observable into a hot observable, so that even if all the subscribers leave and later a new subscriber comes in, the observable would still be active and re-emit the results via replay()
So, I found replay().autoConnect() combination would be apt for my network calls.
In my App, I use fragments all over, and in the BaseFragment I'm keeping a Composite disposable. All my disposables during the fragment lifecycle I keep on adding in the CompositeDisposable and in the onDestroyView event, I'm disposing the disposables, since I don't want to listen for observable emissions when the fragment is not visible.
I have a use-case where I make a network call in one fragment, say FragmentA and then go to FragmentB without killing FragmentA (I'm doing fragment replace). Later I can come back to FragmentA from backstack.
When I move from FragmentA to FragmentB obviously my observers will be disposed, but what I expect is, when I come back to FragmentA later, I should get the old network call results immediately since I'm using replay().autoConnect() on the observable.
But instead some weird thing happens here, I can see the old observable is still there, but it doesn't emit any values.
If I change the mCompositeDisposable.dispose(); to onDestroy() instead of onDestroyView() it works as expected
As per the tutorial, autoconnect() doesn't terminate the observable even if all the observers leave
But in my case, when all the observers leave and later new observer comes in, it doesn't emit only, and since it triggers the doOnsubscribe() all I can see is an infinite loading dialog in the UI (since I'm showing loader on doOnsubscribe). I'm posting a sample code which exactly replicates my issue.
BaseFragment.java
public class BaseFragment extends Fragment{
private CompositeDisposable mCompositeDisposable;
private ProgressDialog mProgressBar;
protected void addToDisposable(Disposable disposable) {
if (mCompositeDisposable == null) mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(disposable);
}
#Override
public void onDestroyView() {
super.onDestroyView();
if (mCompositeDisposable != null) {
mCompositeDisposable.dispose();
}
}
public void showProgress(String message) {
if (mProgressBar == null) {
mProgressBar = new ProgressDialog(getContext());
mProgressBar.setCancelable(false);
}
mProgressBar.setMessage(message);
mProgressBar.show();
}
public void dismissProgress() {
if (mProgressBar != null && mProgressBar.isShowing()) {
mProgressBar.dismiss();
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
replaceFragment(new FragmentA());
}
public void replaceFragment(Fragment fragment) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.addToBackStack(fragment.getClass().getName());
fragmentTransaction.commit();
}
}
FragmentA.java
public class FragmentA extends BaseFragment {
private TextView textView;
private Button button;
private static final String TAG = "FragmentA";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmenta, container, false);
textView = (TextView) view.findViewById(R.id.txt);
button = view.findViewById(R.id.button);
button.setOnClickListener(v -> ((MainActivity) getActivity()).replaceFragment(new FragmentB()));
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
DisposableObserver<List<String>> disposableObserver = Repository.getStringObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(strings -> dismissProgress())
.doOnSubscribe(disposable -> showProgress("Please wait"))
.subscribeWith(new DisposableObserver<List<String>>() {
#Override
public void onNext(List<String> strings) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
stringBuilder.append(string).append("\n");
}
textView.setText(stringBuilder.toString());
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getLocalizedMessage());
}
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
});
addToDisposable(disposableObserver);
}
}
Repository.java
public class Repository {
static Observable<List<String>> listObservable;
public static Observable<List<String>> getStringObservable() {
if (listObservable == null)
listObservable = Observable.fromCallable(() -> {
List<String> strings = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
strings.add("String " + i);
}
Thread.sleep(5000);// mocking network call
return strings;
}).replay(1).autoConnect();
return listObservable;
}
}
FragmentB code is not posted since its empty. Issue can be observed when pressing back from FragmentB.
Hope my issue is clear. Thanks in advance!!

Android exception when popping backstack

This is a followup question to these questions:
popBackStack() after saveInstanceState()
Application crashes in background, when popping a fragment from stack
I am creating an application which uses a service and is reacting to events which are created by the service. One of the events is called within a fragment and is popping from the backstack like this:
getSupportFragmentManager().popBackStack(stringTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
When the app is in the foreground it works fine. When the app is in the background, I get an
IllegalStateException: Can not perform this action after onSaveInstanceState
I have already tried overriding onSaveInstanceState with an empty method.
Why do I get this exception only when the app is in the background and how can I solve it?
Try something like this.
public abstract class PopActivity extends Activity {
private boolean mVisible;
#Override
public void onResume() {
super.onResume();
mVisible = true;
}
#Override
protected void onPause() {
super.onPause();
mVisible = false;
}
private void popFragment() {
if (!mVisible) {
return;
}
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
when you implement the above code alone when you resume the app you will find yourself in a fragment that you actually want to be popped. You can use the following snipped to fix this issue:
public abstract class PopFragment extends Fragment {
private static final String KEY_IS_POPPED = "KEY_IS_POPPED";
private boolean mPopped;
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(KEY_IS_POPPED, mPopped);
super.onSaveInstanceState(outState);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mPopped = savedInstanceState.getBoolean(KEY_IS_POPPED);
}
}
#Override
public void onResume() {
super.onResume();
if (mPopped) {
popFragment();
}
}
protected void popFragment() {
mPopped = true;
// null check and interface check advised
((PopActivity) getActivity()).popFragment();
}
}
Original Author

Facebook FriendPickerFragment showing no friends

Any idea why the list might be empty?
The code is below.
public class PickFBFriendsActivity extends FragmentActivity {
FriendPickerFragment friendPickerFragment;
// A helper to simplify life for callers who want to populate a Bundle with the necessary
// parameters. A more sophisticated Activity might define its own set of parameters; our needs
// are simple, so we just populate what we want to pass to the FriendPickerFragment.
public static void populateParameters(Intent intent, String userId, boolean multiSelect, boolean showTitleBar) {
intent.putExtra(FriendPickerFragment.USER_ID_BUNDLE_KEY, userId);
intent.putExtra(FriendPickerFragment.MULTI_SELECT_BUNDLE_KEY, multiSelect);
intent.putExtra(FriendPickerFragment.SHOW_TITLE_BAR_BUNDLE_KEY, showTitleBar);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pick_friends_activity);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
// First time through, we create our fragment programmatically.
final Bundle args = getIntent().getExtras();
friendPickerFragment = new FriendPickerFragment(args);
friendPickerFragment.setUserId(null);
fm.beginTransaction()
.add(R.id.friend_picker_fragment, friendPickerFragment)
.commit();
} else {
// Subsequent times, our fragment is recreated by the framework and already has saved and
// restored its state, so we don't need to specify args again. (In fact, this might be
// incorrect if the fragment was modified programmatically since it was created.)
friendPickerFragment = (FriendPickerFragment) fm.findFragmentById(R.id.friend_picker_fragment);
}
friendPickerFragment.setOnErrorListener(new PickerFragment.OnErrorListener() {
#Override
public void onError(PickerFragment<?> fragment, FacebookException error) {
PickFBFriendsActivity.this.onError(error);
}
});
friendPickerFragment.setOnDoneButtonClickedListener(new PickerFragment.OnDoneButtonClickedListener() {
#Override
public void onDoneButtonClicked(PickerFragment<?> fragment) {
setResult(RESULT_OK, null);
finish();
}
});
}
private void onError(Exception error) {
String text = getString(R.string.exception, error.getMessage());
Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
toast.show();
}
#Override
protected void onStart() {
super.onStart();
}
}
Note that it's pretty much the same as the sample one.
Figured it out: my onStart() method was incomplete, missing the following line:
friendPickerFragment.loadData(false);
Must have deleted it accidently.

Android. Fragment getActivity() sometimes returns null

In developer console error reports sometimes I see reports with NPE issue. I do not understand what is wrong with my code. On emulator and my device application works good without forcecloses, however some users get NullPointerException in fragment class when the getActivity() method is called.
Activity
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
pager = (ViewPager) findViewById(R.id.pager);
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) adapter.getItem(0));
firstTask.execute();
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
}
AsyncTask class
public class FirstTask extends AsyncTask{
private TaskListener taskListener;
...
#Override
protected void onPostExecute(T result) {
...
taskListener.onTaskComplete(result);
}
}
Fragment class
public class FirstFragment extends Fragment immplements Taskable, TaskListener{
public FirstFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.first_view, container, false);
}
#Override
public void executeTask() {
FirstTask firstTask = new FirstTask(MyActivity.this);
firstTask.setTaskListener(this);
firstTask.execute();
}
#Override
public void onTaskComplete(T result) {
// NPE is here
Resources res = getActivity().getResources();
...
}
}
Maybe this error happens when applications resumed from background. In this case how I should handle this situation properly?
It seems that I found a solution to my problem.
Very good explanations are given here and here.
Here is my example:
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;
#Override
public void onCreate(Bundle savedInstanceState) {
....
this.savedInstanceState = savedInstanceState;
pager = (ViewPager) findViewById(R.id.pager);;
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
if (savedInstanceState == null){
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
}else{
Integer count = savedInstanceState.getInt("tabsCount");
String[] titles = savedInstanceState.getStringArray("titles");
for (int i = 0; i < count; i++){
adapter.addFragment(getFragment(i), titles[i]);
}
}
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) getFragment(0));
firstTask.execute();
}
private Fragment getFragment(int position){
return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}
private String getFragmentTag(int position) {
return "android:switcher:" + R.id.pager + ":" + position;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tabsCount", adapter.getCount());
outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
The main idea in this code is that, while running your application normally, you create new fragments and pass them to the adapter. When you are resuming your application fragment manager already has this fragment's instance and you need to get it from fragment manager and pass it to the adapter.
UPDATE
Also, it is a good practice when using fragments to check isAdded before getActivity() is called. This helps avoid a null pointer exception when the fragment is detached from the activity. For example, an activity could contain a fragment that pushes an async task. When the task is finished, the onTaskComplete listener is called.
#Override
public void onTaskComplete(List<Feed> result) {
progress.setVisibility(View.GONE);
progress.setIndeterminate(false);
list.setVisibility(View.VISIBLE);
if (isAdded()) {
adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
If we open the fragment, push a task, and then quickly press back to return to a previous activity, when the task is finished, it will try to access the activity in onPostExecute() by calling the getActivity() method. If the activity is already detached and this check is not there:
if (isAdded())
then the application crashes.
Ok, I know that this question is actually solved but I decided to share my solution for this. I've created abstract parent class for my Fragment:
public abstract class ABaseFragment extends Fragment{
protected IActivityEnabledListener aeListener;
protected interface IActivityEnabledListener{
void onActivityEnabled(FragmentActivity activity);
}
protected void getAvailableActivity(IActivityEnabledListener listener){
if (getActivity() == null){
aeListener = listener;
} else {
listener.onActivityEnabled(getActivity());
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) activity);
aeListener = null;
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) context);
aeListener = null;
}
}
}
As you can see, I've added a listener so, whenever I'll need to get Fragments Activity instead of standard getActivity(), I'll need to call
getAvailableActivity(new IActivityEnabledListener() {
#Override
public void onActivityEnabled(FragmentActivity activity) {
// Do manipulations with your activity
}
});
The best to get rid of this is to keep activity reference when onAttach is called and use the activity reference wherever needed, for e.g.
#Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Edited, since onAttach(Activity) is depreciated & now onAttach(Context) is being used
Don't call methods within the Fragment that require getActivity() until onStart in the parent Activity.
private MyFragment myFragment;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
myFragment = new MyFragment();
ft.add(android.R.id.content, youtubeListFragment).commit();
//Other init calls
//...
}
#Override
public void onStart()
{
super.onStart();
//Call your Fragment functions that uses getActivity()
myFragment.onPageSelected();
}
I've been battling this kind of problem for a while, and I think I've come up with a reliable solution.
It's pretty difficult to know for sure that this.getActivity() isn't going to return null for a Fragment, especially if you're dealing with any kind of network behaviour which gives your code ample time to withdraw Activity references.
In the solution below, I declare a small management class called the ActivityBuffer. Essentially, this class deals with maintaining a reliable reference to an owning Activity, and promising to execute Runnables within a valid Activity context whenever there's a valid reference available. The Runnables are scheduled for execution on the UI Thread immediately if the Context is available, otherwise execution is deferred until that Context is ready.
/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {
/** A class which defines operations to execute once there's an available Context. */
public interface IRunnable {
/** Executes when there's an available Context. Ideally, will it operate immediately. */
void run(final Activity pActivity);
}
/* Member Variables. */
private Activity mActivity;
private final List<IRunnable> mRunnables;
/** Constructor. */
public ActivityBuffer() {
// Initialize Member Variables.
this.mActivity = null;
this.mRunnables = new ArrayList<IRunnable>();
}
/** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
public final void safely(final IRunnable pRunnable) {
// Synchronize along the current instance.
synchronized(this) {
// Do we have a context available?
if(this.isContextAvailable()) {
// Fetch the Activity.
final Activity lActivity = this.getActivity();
// Execute the Runnable along the Activity.
lActivity.runOnUiThread(new Runnable() { #Override public final void run() { pRunnable.run(lActivity); } });
}
else {
// Buffer the Runnable so that it's ready to receive a valid reference.
this.getRunnables().add(pRunnable);
}
}
}
/** Called to inform the ActivityBuffer that there's an available Activity reference. */
public final void onContextGained(final Activity pActivity) {
// Synchronize along ourself.
synchronized(this) {
// Update the Activity reference.
this.setActivity(pActivity);
// Are there any Runnables awaiting execution?
if(!this.getRunnables().isEmpty()) {
// Iterate the Runnables.
for(final IRunnable lRunnable : this.getRunnables()) {
// Execute the Runnable on the UI Thread.
pActivity.runOnUiThread(new Runnable() { #Override public final void run() {
// Execute the Runnable.
lRunnable.run(pActivity);
} });
}
// Empty the Runnables.
this.getRunnables().clear();
}
}
}
/** Called to inform the ActivityBuffer that the Context has been lost. */
public final void onContextLost() {
// Synchronize along ourself.
synchronized(this) {
// Remove the Context reference.
this.setActivity(null);
}
}
/** Defines whether there's a safe Context available for the ActivityBuffer. */
public final boolean isContextAvailable() {
// Synchronize upon ourself.
synchronized(this) {
// Return the state of the Activity reference.
return (this.getActivity() != null);
}
}
/* Getters and Setters. */
private final void setActivity(final Activity pActivity) {
this.mActivity = pActivity;
}
private final Activity getActivity() {
return this.mActivity;
}
private final List<IRunnable> getRunnables() {
return this.mRunnables;
}
}
In terms of its implementation, we must take care to apply the life cycle methods to coincide with the behaviour described above by Pawan M:
public class BaseFragment extends Fragment {
/* Member Variables. */
private ActivityBuffer mActivityBuffer;
public BaseFragment() {
// Implement the Parent.
super();
// Allocate the ActivityBuffer.
this.mActivityBuffer = new ActivityBuffer();
}
#Override
public final void onAttach(final Context pContext) {
// Handle as usual.
super.onAttach(pContext);
// Is the Context an Activity?
if(pContext instanceof Activity) {
// Cast Accordingly.
final Activity lActivity = (Activity)pContext;
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(lActivity);
}
}
#Deprecated #Override
public final void onAttach(final Activity pActivity) {
// Handle as usual.
super.onAttach(pActivity);
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(pActivity);
}
#Override
public final void onDetach() {
// Handle as usual.
super.onDetach();
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextLost();
}
/* Getters. */
public final ActivityBuffer getActivityBuffer() {
return this.mActivityBuffer;
}
}
Finally, in any areas within your Fragment that extends BaseFragment that you're untrustworthy about a call to getActivity(), simply make a call to this.getActivityBuffer().safely(...) and declare an ActivityBuffer.IRunnable for the task!
The contents of your void run(final Activity pActivity) are then guaranteed to execute along the UI Thread.
The ActivityBuffer can then be used as follows:
this.getActivityBuffer().safely(
new ActivityBuffer.IRunnable() {
#Override public final void run(final Activity pActivity) {
// Do something with guaranteed Context.
}
}
);
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
}
I know this is a old question but i think i must provide my answer to it because my problem was not solved by others.
first of all : i was dynamically adding fragments using fragmentTransactions.
Second: my fragments were modified using AsyncTasks (DB queries on a server).
Third: my fragment was not instantiated at activity start
Fourth: i used a custom fragment instantiation "create or load it" in order to get the fragment variable.
Fourth: activity was recreated because of orientation change
The problem was that i wanted to "remove" the fragment because of the query answer, but the fragment was incorrectly created just before. I don't know why, probably because of the "commit" be done later, the fragment was not added yet when it was time to remove it. Therefore getActivity() was returning null.
Solution :
1)I had to check that i was correctly trying to find the first instance of the fragment before creating a new one
2)I had to put serRetainInstance(true) on that fragment in order to keep it through orientation change (no backstack needed therefore no problem)
3)Instead of "recreating or getting old fragment" just before "remove it", I directly put the fragment at activity start.
Instantiating it at activity start instead of "loading" (or instantiating) the fragment variable before removing it prevented getActivity problems.
In Kotlin you can try this way to handle getActivity() null condition.
activity?.let { // activity == getActivity() in java
//your code here
}
It will check activity is null or not and if not null then execute inner code.

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?
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.
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(...)
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! :)
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
}
}
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;
}
}
}
if (getActivity() == null) return;
works also in some cases. Just breaks the code execution from it and make sure the app not crash
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.
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!
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.
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);
}
In my case fragment methods have been called after
getActivity().onBackPressed();
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);
}
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
simple solution and work 100%
if (getActivity() == null || !isAdded()) return;

Categories

Resources