I was wondering, what is the Fragment lifecycle methods, I should commit FragmentTransaction to avoid famous
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
According to http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html, it gives great tip, on how to avoid such exception, by commit FragmentTransaction
FragmentActivity
onCreate()
onResumeFragments()
onPostResume()
Fragment
???
However, how about Fragment? What is the suitable Fragment lifecycle we should commit our fragment? For instance, under very rare situation, I will get exception from Google Play Console crash report, while trying to commit Fragment in another Fragment's onCreate.
public class BuyPortfolioFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentManager fm = this.getFragmentManager();
// Check to see if we have retained the worker fragment.
this.statusBarUpdaterFragment = (StatusBarUpdaterFragment)fm.findFragmentByTag(STATUS_BAR_UPDATER_FRAGMENT);
if (this.statusBarUpdaterFragment == null) {
this.statusBarUpdaterFragment = StatusBarUpdaterFragment.newInstance();
this.statusBarUpdaterFragment.setTargetFragment(this, 0);
// java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
fm.beginTransaction().add(statusBarUpdaterFragment, STATUS_BAR_UPDATER_FRAGMENT).commit();
} else {
statusBarUpdaterFragment.setTargetFragment(this, 0);
}
p/s I know I can avoid such exception by using commitAllowingStateLoss. I want to use it as last resource.
Fragment's lifecycle state not always matches Activity's. Fragment's method getFragmentManager() returns the FragmentManager of it's hosting Activity (unless it's a child Fragment, if so this method returns the child fragment manager of a hosting Fragment). You may never know in which state is Fragment's hosting Activity unless you make tracking code. So it's really possible that the transaction eventually may be committed after Activity onSaveInstanceState() was called.
I suggest using getChildFragmentManager() and deal with child fragments from fragments.
Or if your intention was really to control Activity Fragments, make accessors for controlling it's state, like
// Activity method
public void showSomeFragment() {
if (mFragmentTransactionsAllowed) {
// do transaction
}
}
// And track the boolean
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
// override on onCreate() in case if Activity object is reused and state was true
mFragmentTransactionsAllowed = true;
}
#Override
protected void onStart() {
super.onStart();
// override here so that if activity goes foreground but not yet destroyed
mFragmentTransactionsAllowed = true;
}
#Override
protected void onResume() {
super.onResume();
mFragmentTransactionsAllowed = true;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mFragmentTransactionsAllowed = false;
}
I have implemented a simple activity with this code:
public class MainActivity extends AppCompatActivity implements Fragment_1.Operations{
Fragment_1 fragment_1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmentactivity);
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//called on buttonclick, fired from a button existing in R.layout.fragmentactivity
public void createFragment(View view){
if (getSupportFragmentManager().findFragmentByTag("fragment_1")==null){
fragment_1=new Fragment_1();
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragmentactt,fragment_1,"fragment_1");
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
else{
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//Simply adding item to the listview contained in fragment_1.
fragment_1.add("Project #1");
fragment_1.add("Project #2");
fragment_1.add("Project #3");
}
//callback of interface "Operations"
#Override
public void buttonClicked() {
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
if (fragment_1.isAdded()){
transaction.remove(fragment_1);
//transaction.addToBackStack(null);
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
}
Well, the doubt came from the fact that no "onSaveInstanceState" needed to be implemented, everything got "saved" without any problems.
So, why should i use putfragment and getFragment? Do these methods need to be called in order to avoid that, when OS kills app process, they would be lost? This is the only reason i can imagine to force onSaveInstanceState and onRestoreInstanceState methods.
Any help is appreciated.
Activity and fragment lifecycles are linked so when any callback method such as onResume is called for the activity, it is called for the fragment too.
putFragment and getFragment help the activity to manage its fragment child's lifecycle. The activity also has to save instance state.
In order to be activity independant, a fragment can manage his own instance state.
So Adopting The Design Pattern described here whereby a Async Task wrapped around a retained fragment via the setRetainInstance as seen described here
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
Im having an issue where a task is started before the worker fragment can attach to the activity leaving the mCallBack to be null at the same time onPreExecute is called which throws a nullPointerException/
This is largely due to the fact that the async task is started via a method call (a start method which creates an instance of the inner async task) and not inside onCreate or OnActivityCreated since the task can be started again (under a new instance of it) and cannot be done so in onCreate or OnActivity created since the life cycle of these methods only call 1 time because of the setRetainInstance as far I as know.
My question is where a way to program some sort of routine whereby a wait until fragment has attached to activity routine and if so do the task that you need to do?
Many thanks.
Update1: Code Posted
This is how the worker fragment is being added. When the user presses the send feedback button. This OnClick Listener is invoked. The final if statement you see is what starts the async task. Note that this code is inside another fragment.
#Override
public void onClick(View v)
{
// TODO Auto-generated method stub
FragmentManager fm = getActivity().getSupportFragmentManager();
mThreadFragment = (ConnectToServerForResults) fm.findFragmentByTag(ConnectToServerForResults.class.getName());
if(mThreadFragment == null)
{
mThreadFragment = new ConnectToServerForResults();
fm.beginTransaction().add(mThreadFragment, ConnectToServerForResults.class.getName()).commit();
}
if(!mThreadFragment.isRunning())
{
mThreadFragment.startSendFeedBack(3, preventSQLInjections(),getResources().getString(R.string.app_name));
}
else
{
//Work in progress :D
}
}
Worker Fragment Start Task Method
public void startSendFeedBack(int requestCode,String feedback,String appName)
{
XAMPP_URL ="http://10.0.2.2/testStuff/feed.php";
http = new HTTPConnection();
http.execute(String.valueOf(requestCode),XAMPP_URL,feedback,appName);
mRunning = true;
}
Worker Fragment OnAttach
#Override
public void onAttach(Activity activity)
{
// TODO Auto-generated method stub
super.onAttach(activity);
Log.d("ERSEN", "onAttach");
if(!(activity instanceof ResultAsyncTaskCallbacks))
{
throw new IllegalStateException("activity must implement the AsyncTaskCallbacks interface.");
}
mCallback = (ResultAsyncTaskCallbacks) activity;
}
so I made a solution which solved the problems. May not be the best solution but my problems appear resolved.
I added a call back to my interface called onThreadFragmentAttached()
public static interface ResultAsyncTaskCallbacks
{
void onPreExecuteResults();
void onCancelledResults();
void onPostExecuteResults();
void onThreadFragmentAttached();
}
I added a class variable to the worker fragment
private boolean isFirstTime=true;
This Boolean at the start says that the fragment is being added for the first time
In OnActivityCreated I can safely assume that the fragment is attached because this is what that method signifies.
In there is this logic. Check if firstTime is true (yes would be if the fragment is added for the first time). If call onThreadFragmentAttached();. Set firstTime to false because the fragment is added. This false is preserved through config changes because this fragment is retained.
if(isFirstTime == true)
{
mCallback.onThreadFragmentAttached();
isFirstTime = false;
}
In main activity which implements the callbacks
#Override
public void onThreadFragmentAttached(int fragmentCode)
{
FB = (FeedBack)fragmentManger.findFragmentByTag(FeedBack.class.getName());
FB.sendFeedback();
}
Find the feedback fragment (this was my fragment which needed to start a task) and call sendFeedBack method.
This is the method which simply calls the start method.
public void sendFeedback()
{
mThreadFragment.startSendFeedBack(3, preventSQLInjections(),getResources().getString(R.string.app_name));
}
In my onClick I have the following logic
//Check if there is not a task running at the moment
if(!mThreadFragment.isRunning()) //No task
{
//if its the first time worker fragment is being added it will start the task itself via the onThreadFragmentAttached callback (inside main activity) which then calls the sendFeedBack method that starts the task
if(mThreadFragment.getIsFirstTime() != true) //this is here in cases where the thread fragment is already attached and the task can be started as normal.
{
sendFeedback();
}
}
This checks if the fragment has already been added and just to go ahead and start the task. Note the getIsFirstTime method just returns the value of the class variable isFirstTime. If its false proceed as normal because its already attached. if it was true this would be bypassed and the task would start anyway via the onThreadAttachedCallback();
I am using a Fragment class.
I add it using
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Search fragment = new Search(maincontrolActivity.this);
fragmentTransaction.add(R.id.mainLayout,fragment , "MY_FRAG");
fragmentTransaction.commit();
this.FragmentObject = fragment
when I do refresh the control,I recall this code but by passing this.FragmentObject but I think it be garbage collected because the = refere to the same object , and when say add, it free the old fragement which is the same
so do I need a deep copy or any way to refresh ?
any idea
Ok. So what I would do is have an Interface defined and have each Fragment register with the Activity for a callback when the refresh button is clicked. Then in the fragment itself have it refresh its data. If that means getting new data, switch to a ProgressBar, get the data from the server, and repopulate the Views. Here is an entire article on creating Interfaces in Activities and calling Fragments from them.
Here is roughly what your code will look like...
The Activity:
public class RefreshActivity extends Activity implements View.OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
findViewById(R.id.refresh_button).setOnClickListener(this);
}
public interface OnRefreshPressedListener {
public void onRefreshPressed();
}
#Override
public void onClick(View v) {
((OnRefreshPressedListener)this.FragmentObject).onRefreshPressed();
}
}
The Fragment:
public class Search extends Fragment implements OnRefreshPressedListener {
#Override
public void onRefreshPressed() {
//TODO: Refresh your data!
}
}
I have a fragment (F1) with a public method like this
public void asd() {
if (getActivity() == null) {
Log.d("yes","it is null");
}
}
and yes when I call it (from the Activity), it is null...
FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();
It must be something that I am doing very wrong, but I don't know what that is.
commit schedules the transaction, i.e. it doesn't happen straightaway but is scheduled as work on the main thread the next time the main thread is ready.
I'd suggest adding an
onAttach(Activity activity)
method to your Fragment and putting a break point on it and seeing when it is called relative to your call to asd(). You'll see that it is called after the method where you make the call to asd() exits. The onAttach call is where the Fragment is attached to its activity and from this point getActivity() will return non-null (nb there is also an onDetach() call).
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(activity);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
This happened when you call getActivity() in another thread that finished after the fragment has been removed. The typical case is calling getActivity() (ex. for a Toast) when an HTTP request finished (in onResponse for example).
To avoid this, you can define a field name mActivity and use it instead of getActivity(). This field can be initialized in onAttach() method of Fragment as following:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
mActivity =(Activity) context;
}
}
In my projects, I usually define a base class for all of my Fragments with this feature:
public abstract class BaseFragment extends Fragment {
protected FragmentActivity mActivity;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
mActivity =(Activity) context;
}
}
}
Happy coding,
The other answers that suggest keeping a reference to the activity in onAttach are just suggesting a bandaid to the real problem. When getActivity returns null it means that the Fragment is not attached to the Activity. Most commonly this happens when the Activity has gone away due to rotation or the Activity being finished but the Fragment still has some kind of callback listener registered preventing it from being garbage collected. When the listener gets called if you need to do something with the Activity but the Activity is gone there isn't much you can do. In your code you should just check getActivity() != null and if it's not there then don't do anything. If you keep a reference to the Activity that is gone you are preventing the Activity from being garbage collected. Any UI things you might try to do won't be seen by the user. I can imagine some situations where in the callback listener you want to have a Context for something non-UI related, in those cases it probably makes more sense to get the Application context. Note that the only reason that the onAttach trick isn't a big memory leak is because normally after the callback listener executes it won't be needed anymore and can be garbage collected along with the Fragment, all its View's and the Activity context. If you setRetainInstance(true) there is a bigger chance of a memory leak because the Activity field will also be retained but after rotation that could be the previous Activity not the current one.
Since Android API level 23, onAttach(Activity activity) has been deprecated. You need to use onAttach(Context context). http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)
Activity is a context so if you can simply check the context is an Activity and cast it if necessary.
#Override
public void onAttach(Context context) {
super.onAttach(context);
Activity a;
if (context instanceof Activity){
a=(Activity) context;
}
}
PJL is right.
I have used his suggestion and this is what i have done:
defined global variables for fragment:
private final Object attachingActivityLock = new Object();
private boolean syncVariable = false;
implemented
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
synchronized (attachingActivityLock) {
syncVariable = true;
attachingActivityLock.notifyAll();
}
}
3 . I wrapped up my function, where I need to call getActivity(), in thread, because if it would run on main thread, i would block the thread with the step 4. and onAttach() would never be called.
Thread processImage = new Thread(new Runnable() {
#Override
public void run() {
processImage();
}
});
processImage.start();
4 . in my function where I need to call getActivity(), I use this (before the call getActivity())
synchronized (attachingActivityLock) {
while(!syncVariable){
try {
attachingActivityLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If you have some UI updates, remember to run them on UI thread. I need to update ImgeView so I did:
image.post(new Runnable() {
#Override
public void run() {
image.setImageBitmap(imageToShow);
}
});
The order in which the callbacks are called after commit():
Whatever method you call manually right after commit()
onAttach()
onCreateView()
onActivityCreated()
I needed to do some work that involved some Views, so onAttach() didn't work for me; it crashed. So I moved part of my code that was setting some params inside a method called right after commit() (1.), then the other part of the code that handled view inside onCreateView() (3.).
I am using OkHttp and I just faced this issue.
For the first part #thucnguyen was on the right track.
This happened when you call getActivity() in another thread that finished after the fragment has been removed. The typical case is calling getActivity() (ex. for a Toast) when an HTTP request finished (in onResponse for example).
Some HTTP calls were being executed even after the activity had been closed (because it can take a while for an HTTP request to be completed). I then, through the HttpCallback tried to update some Fragment fields and got a null exception when trying to getActivity().
http.newCall(request).enqueue(new Callback(...
onResponse(Call call, Response response) {
...
getActivity().runOnUiThread(...) // <-- getActivity() was null when it had been destroyed already
IMO the solution is to prevent callbacks to occur when the fragment is no longer alive anymore (and that's not just with Okhttp).
The fix: Prevention.
If you have a look at the fragment lifecycle (more info here), you'll notice that there's onAttach(Context context) and onDetach() methods. These get called after the Fragment belongs to an activity and just before stop being so respectively.
That means that we can prevent that callback to happen by controlling it in the onDetach method.
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Initialize HTTP we're going to use later.
http = new OkHttpClient.Builder().build();
}
#Override
public void onDetach() {
super.onDetach();
// We don't want to receive any more information about the current HTTP calls after this point.
// With Okhttp we can simply cancel the on-going ones (credits to https://github.com/square/okhttp/issues/2205#issuecomment-169363942).
for (Call call : http.dispatcher().queuedCalls()) {
call.cancel();
}
for (Call call : http.dispatcher().runningCalls()) {
call.cancel();
}
}
Where do you call this function? If you call it in the constructor of Fragment, it will return null.
Just call getActivity() when the method onCreateView() is executed.
Do as follows. I think it will be helpful to you.
private boolean isVisibleToUser = false;
private boolean isExecutedOnce = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_my, container, false);
if (isVisibleToUser && !isExecutedOnce) {
executeWithActivity(getActivity());
}
return root;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
if (isVisibleToUser && getActivity()!=null) {
isExecutedOnce =true;
executeWithActivity(getActivity());
}
}
private void executeWithActivity(Activity activity){
//Do what you have to do when page is loaded with activity
}
Those who still have the problem with onAttach(Activity activity), Its just changed to Context -
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
In most cases saving the context will be enough for you - for example if you want to do getResources() you can do it straight from the context. If you still need to make the context into your Activity do so -
#Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity a; //Your activity class - will probably be a global var.
if (context instanceof mActivity){
a=(mActivity) context;
}
}
As suggested by user1868713.
Another good solution would be using Android's LiveData with MVVM architecture.
You would define a LiveData object inside your ViewModel and observe it in your fragment, and when LiveData value is changed, it would notify your observer (fragment in this case) only if your fragment is in active state, so it would be guaranteed that you would make your UI works and access the activity only when your fragment is in active state. This is one advantage that comes with LiveData
Of course when this question was first asked, there was no LiveData. I am leaving this answer here because as I see, there is still this problem and it could be helpful to someone.
Call getActivity() method inside the onActivityCreated()
I have solved my problem this way.I have passed getApplicationContext from the previous class which has already access of getApplicationContext.I have passed Inputstream object to my new class Nutrients.
try{
InputStream is= getApplicationContext().getAssets().open("nutrient_list.json");
Nutrients nutrients=Nutrients.getNutrients(topRecognition,is);
} catch (IOException e) {
e.printStackTrace();
}
Write a common method that will ensure you will never get null Activity.
public class BaseFragment extends Fragment {
private Context contextNullSafe;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
/*View creation related to this fragment is finished here. So in case if contextNullSafe is null
* then we can populate it here.In some discussion in - https://stackoverflow.com/questions/6215239/getactivity-returns-null-in-fragment-function
* and https://stackoverflow.com/questions/47987649/why-getcontext-in-fragment-sometimes-returns-null,
* there are some recommendations to call getContext() or getActivity() after onCreateView() and
* onViewCreated() is called after the onCreateView() call every time */
if (contextNullSafe == null) getContextNullSafety();
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
contextNullSafe = context;
}
/**CALL THIS IF YOU NEED CONTEXT*/
public Context getContextNullSafety() {
if (getContext() != null) return getContext();
if (getActivity() != null) return getActivity();
if (contextNullSafe != null) return contextNullSafe;
if (getView() != null && getView().getContext() != null) return getView().getContext();
if (requireContext() != null) return requireContext();
if (requireActivity() != null) return requireActivity();
if (requireView() != null && requireView().getContext() != null)
return requireView().getContext();
return null;
}
/**CALL THIS IF YOU NEED ACTIVITY*/
public FragmentActivity getActivityNullSafety() {
if (getContextNullSafety() != null && getContextNullSafety() instanceof FragmentActivity) {
/*It is observed that if context it not null then it will be
* the related host/container activity always*/
return (FragmentActivity) getContextNullSafety();
}
return null;
}