Refresh fragment is not working any more? - android

I lost some hours today because my code was not working any more.
The code to reload the view of a fragment was not working anymore after updating to the new version of Support Library 25.1.0:
This is my code :
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.detach(fragment);
fragmentTransaction.attach(fragment);
fragmentTransaction.commit();
I have tried to debug putting some breakpoints on
public void onPause()
public void onStop()
public void onAttach(Context context)
public void onDetach()
public void onDestroyView()
public void onDestroy()
but the application is not entering into any of that function and nothing happened on the screen.
If I call detach alone, without attach, the application enter in onPause and onStop and the view leave the screen.

Faced a similar issue after updating androidx.navigation from 2.3.1 to 2.3.2.
parentFragmentManager.beginTransaction().detach(this).attach(this).commit()
has stopped reload the view of a fragment.
Everything that I found here did not help, but I noticed that separately the detach and attach operations are being performed successfully, I decided to spread their execution to different FragmentTransaction entities:
parentFragmentManager.beginTransaction().detach(this).commit ()
parentFragmentManager.beginTransaction().attach(this).commit ()
and it worked.
Hope this saves someone some time.

I've found myself facing the same issue, and found no answer online. Finally I've found out that some optimizations were added to Fragment Transactions in Revision 25.1.1 of the Support Library. (see https://developer.android.com/topic/libraries/support-library/revisions.html#25-1-1). Disabling those for your transaction will make it work as expected:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setAllowOptimization(false);
transaction.detach(fragment).attach(fragment).commitAllowingStateLoss();
Update
setAllowOptimization is deprecated. Use setReorderingAllowed instead.

#Viad actually answered the question. To add a little bit to it, this happens in android versions 26 and above where reordering is allowed by default. Reordering comes into play when two fragment operations are requested at the same done, for example adding fragment 1 and then replacing it with fragment 2, which causes only the latter (replacing fragment 2) to happen.
So when reordering is allowed by default, when restarting the fragment using detach(fragment).attach(fragment) the first one is ignored and only second one is executed. As the fragment is already attached, attach(fragment) does not do anything. This is why none of the lifecycle methods of the fragment is called.
The resolution to the problem would be to setReorderingAllowed(false) to deactivate reordering. So the solution would be:
FragmentTransaction transaction = mActivity.getFragmentManager()
.beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
transaction.setReorderingAllowed(false);
}
transaction.detach(fragment).attach
(fragment).commit();

Here is a slight modification I made to use getSupportFragmentManager:
FragmentTransaction t = getActivity().getSupportFragmentManager().beginTransaction();
t.setAllowOptimization(false);
t.detach(this).attach(this).commitAllowingStateLoss();

To achieve the fragment's refresh with Androidx is a bit different.
As per Android Developers documentation here
As a FragmentTransaction is treated as a single atomic set of
operations, calls to both detach and attach on the same fragment
instance in the same transaction effectively cancel each other out,
thus avoiding the destruction and immediate recreation of the
fragment's UI. Use separate transactions, separated by
executePendingOperations() if using commit(), if you want to detach
and then immediately re-attach a fragment.
Therefore the code must be like this :
fun FragmentActivity.relaunchFragment(fragmentId: Int) {
val currentFragment = supportFragmentManager.findFragmentById(fragmentId)
val detachTransaction = supportFragmentManager.beginTransaction()
val attachTransaction = supportFragmentManager.beginTransaction()
currentFragment?.let {
detachTransaction.detach(it)
attachTransaction.attach(it)
detachTransaction.commit()
attachTransaction.commit()
}
}

Related

Using ViewPager2 with TabLayout and fragments. Able to swap fragments, updated fragment not showing unless the user browses to another tab first

I am updating an application that was written in 2014. The fragment that I would like to update (refresh) uses the following code,
public void fragmentRefresh(View view) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).addToBackStack(null).commit();
}
The application does not display the updated fragment until I load another tab and then browse back. A sample application has been created with four tabs to investigate (and illustrate) this issue (see Figure 1 below).
This example application is available on GitHub here: https://github.com/portsample/moderntablayout
Question(s): The version of the application that I wrote in 2014 (not the example on Github) would update the view after a fragment swap using the old version of ViewPager using methods similar to those presented here with no problem. Is the current issue a function of this method diverging from Google "best practices" and not fully supported by ViewPager2 and the other updates? What is the best remedy, (i.e. most widely accepted solution) for this issue?
Thanks in advance for cogent and constructive responses.
I believe I had the same issue before, I solved it by using the onResume() and onPause() methods, you can read about them in fragment lifecycle
so what you have to do is to create a function for what you want to refresh instead of attaching and detaching the fragment. in my example I was creating button and getting their data from my database so instead of the attach method, I call the function and every time the user add something to the database, and return to the fragment it will automatically refresh because of the onResume()
example:
private var shouldRefreshOnResume = false
...
override fun onCreateView() {
//call your function here
}
override fun onResume() {
super.onResume()
//call your function here again
}
override fun onPause() {
super.onPause()
shouldRefreshOnResume = true
}

Is there a good approach to remove/add/hide/show a group of Fragments in an Activity?

My app is the following:
First screen: login page (Fragment). After the user logs in, replaces it with the next screens.
Other screens: within the app the user can see about 5 different Fragments, including a map, and they are all related.
I am having problems with the logout feature. I want to, regardless where in the app the user is, remove all possible Fragments when the user logs out. The problem is that my map may be hidden instead of removed.
I tried to implement: fragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);
But it works buggy and intermittently. Sometimes it works, sometimes I get a java.lang.IllegalStateException: Fragment already added exception for my LoginFragment.
Is there a way to safely remove/stop/destroy a whole set of Fragments?
Let me know if there are better practices in this scenario.
Thanks!
Edit:
I'm using this (safer?) method to clear my backstack:
private void clearBackStack() {
FragmentManager manager = getFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry first = manager.getBackStackEntryAt(0);
manager.popBackStack(first.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
What I ended up doing was detaching all my fragments, so they'd be stopped. However, I had resources (audio) being initialized on the Fragment creation, and even detached they would still be running (playing the audio). I then came up with this method to be sure I'd free all my resources, and ran it with all my fragments:
public void safeDetach(Fragment f) {
try {
if (f.isAdded()) {
getFragmentManager().beginTransaction()
.detach(f)
.commit();
f.onDestroy();
}
catch (Exception e) { }
}
Now it works, no matter when or where I try to log out.
You can tag your fragments before adding them to your activity like this:
FooFragment fragment = new FooFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.container, fragment, "foo");
transaction.addToBackStack("foo");
transaction.commit();
This way you can do many things when you find your fragment using the Fragment Manager:
currentFragment = getFragmentManager().findFragmentByTag("foo");
if (currentFragment != null) {
//This means your fragment has been added to your backstack before
if (currentFragment.isVisible ()) {
// Is currently visible to the user
if (currentFragment.isResumed())
// Is resumed... etc...
}
}
I usually pop from the backstack using the "immediate" method, just because I'm tagging the fragments and always know which one is currently available:
getFragmentManager().popBackStackImmediate();
Have fun!

putFragment() - Fragment x is not currently in the FragmentManager

The above title has been asked a lot, but answers seem closely tied to FragmentStatePagerAdapter which has nothing to do with my problem. I'm using the method putFragment(Bundle, String, Fragment) directly.
The Android Documentation for putFragment(Bundle, String, Fragment) says:
Put a reference to a fragment in a Bundle. This Bundle can be persisted as saved state, and when later restoring getFragment(Bundle, String) will return the current instance of the same fragment.
Parameters
* bundle The bundle in which to put the fragment reference.
* key The name of the entry in the bundle.
* fragment The Fragment whose reference is to be stored.
However the following code throws an exception:
Bundle bundle = new Bundle();
CustomFragment actionBarFragment = getActionBarFragment();
CustomFragment contentFragment = getContentFragment();
actionBarFragment.setArguments(bundle);
contentFragment.setArguments(bundle);
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.add(R.id.actionBarPane, actionBarFragment);
mTransaction.add(R.id.contentPane, contentFragment);
mTransaction.commit();
getSupportFragmentManager().putFragment(bundle, "ContentFragment", contentFragment);
getSupportFragmentManager().putFragment(bundle, "ActionBar", actionBarFragment);
The reason I'm using the above is so that both ContentFragment and ActionBar fragment can use the result of getArguments() to find their opposite number, even if they aren't currently in the top of the backstack - such as if they are partially occluded by transparent Fragments higher in the stack.
However, I get the exception:
11-20 13:44:17.842: E/Default Exception Handler(12466): Caused by: java.lang.IllegalStateException: Fragment CustomFragment{4219bdb8 id=0x7f06002e com.test.CustomFragment1} is not currently in the FragmentManager
Can I conclude from this that commit() simply puts the transaction on a stack to be done on the UI thread and the putFragment() calls are happening before that transaction is being carried out? Or am I misunderstanding something? (The documentation on the Android site doesn't say anything about prerequisite states for the Fragments to be in which I assume it should).
It's worth noting the text in commit() which is why I assume the call is happening too early - A possible answer being how to attach a listener to a transaction to notify you when it has been commit()ed. I just don't think that exists...
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
EDIT
Confirmed that commit() is the problem by using a terrible solution:
mTransaction.commit();
new Thread() {
public void run() {
while(!contentFragment.isAdded()) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
getSupportFragmentManager().putFragment(bundle, "ContentFragment", contentFragment);
getSupportFragmentManager().putFragment(bundle, "ActionBar", actionBarFragment);
};
}.start();
Real solutions still very much appreciated.
I've never used it myself, but have you looked at FragmentManager.executePendingTransactions()? Here's what the documentation says:
After a FragmentTransaction is committed with
FragmentTransaction.commit(), it is scheduled to be executed
asynchronously on the process's main thread. If you want to
immediately executing any such pending operations, you can call this
function (only from the main thread) to do so. Note that all callbacks
and other related behavior will be done from within this call, so be
careful about where this is called from.
It sounds like it matches your use case.

Replace fragment using FragmentTransaction calls fragment create multiple times

Here is my code
public void changeFragment(Fragment contentFragment, Fragment lastFragment, int resourseID,int animationType, boolean addToStack) {
if (contentFragment != lastFragment) {
mContentFragmentTransaction = getFragmentManager().beginTransaction();
FragmentTransactionExtended fragmentTransactionExtended = new FragmentTransactionExtended(this, mContentFragmentTransaction,lastFragment,contentFragment, resourseID);
fragmentTransactionExtended.addTransition(animationType);
mContentFragmentTransaction.replace(resourseID, contentFragment);
if (addToStack) {
mContentFragmentTransaction.addToBackStack(null);
}
mContentFragmentTransaction.commit();
}
}
The problem is, everytime I changeFragment using this method, the fragment's oncreate will fire multiple times, first time, it runs once, second time twice , third time three times, hope someone know what's happening here.
addToBackstack creates a snapshot of your fragments state. Which means when you press the back button, you are actually reverting to the last state that addToBackstack was called on.
You have to call replace on the container that has the fragment, not the fragment itself. So instead of calling replace(R.id.dashboard, fragment) it should be replace(R.id.dashboards_container, fragment).
have you tried to use an other method, like remove(), then do an add(). or anything similar? some people say's replace() method does not always behave correctly.
If the state of the activity has already been saved its no longer safe to call commit. You must call commitAllowingStateLoss() instead.
Even if you use replace or remove calls you can't do this. Only work around I have found is to create a new instance of a fragment and add it every time. Once you remove or replace a fragment it is best to drop all of your references to it so the GC can take care of it.
regards maven

Transaction of fragments in android results in blank screen

If it helps, what I want is similar to what is done in this google tutorial
But there a fragment is created prior to the transition. If I do that the transition works fine; but I can't use this approach
=====
Aiming to API 7+ I am just trying to have one Fragment visible in the whole screen and using a button (a drawn button, with an onTouch event) then alternate to a second fragment and viceversa.
But I either get a blank screen when I replace the first fragment with the second, or if I use fragmentTransaction.show and fragmentTransaction.hide; I can switch two times before I get blank screen. I dont want to have on backstack.
I am creating the fragments in MainActivity's onCreate:
DiceTable diceTable = new DiceTable();
Logger logger = new Logger();
fragmentTransaction.add(diceTable, DICETABLE_TAG);
fragmentTransaction.add(logger, LOGGER_TAG);
fragmentTransaction.add(R.id.fragment_container, logger);
fragmentTransaction.add(R.id.fragment_container, diceTable);
Then on one method (called from the fragments) I do the switch:
Logger logger = (Logger)fragmentManager.findFragmentByTag(LOGGER_TAG);
DiceTable diceTable = (DiceTable)fragmentManager.findFragmentByTag(DICETABLE_TAG);
if (diceTable.isVisible()) {
fragmentTransaction.replace(R.id.fragment_container, logger);
fragmentTransaction.commit();
fragmentTransaction.hide(diceTable);
fragmentTransaction.show(logger);
}
else if (logger.isVisible()) {
fragmentTransaction.replace(R.id.fragment_container, diceTable);
fragmentTransaction.commit();
fragmentTransaction.hide(logger);
fragmentTransaction.show(diceTable);
}
This is not how I should do this?
Blank screen when replacing fragments
Try to initialize fragments in that way:
private void initFragments() {
mDiceTable = new DiceTable();
mLogger = new Logger();
isDiceTableVisible = true;
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, mDiceTable);
ft.add(R.id.fragment_container, mLogger);
ft.hide(mLogger);
ft.commit();
}
And then flip between them in that way:
private void flipFragments() {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (isDiceTableVisible) {
ft.hide(mDiceTable);
ft.show(mLogger);
} else {
ft.hide(mLogger);
ft.show(mDiceTable);
}
ft.commit();
isDiceTableVisible = !isDiceTableVisible;
}
You are combining two different methods of changing which Fragment is shown:
Calling replace() to replace the contents of the container with a different Fragment
Calling hide() to remove a Fragment, then calling show() to show another Fragment.
Pick one method and stick with it. The Building a Flexible UI guide uses just the replace() method, so I would start by trying to remove all of your calls to show() and hide().
Also see Android Fragments: When to use hide/show or add/remove/replace? for a quick summary of when it might be beneficial to use hide/show instead of replace.
In case anyone has tried all of the already suggested answers and is still facing the issue, like I was a few moments ago, the problem may come from a different source than your fragments.
In my case, I was switching between fragments correctly, but the reason for the blank screen was that in each new fragment I was trying to asynchronously load images by starting a new Thread every time (something a bit like this) instead of the recommended AsyncTask, or better yet the newSingleThreadExecutor, since AsyncTask is deprecated.
I disturbed the background enough that the fragment was just not showing up unless I navigated to a different app then back.
So unless your mistake is similar to mine my suggestion might be kind of vague but try to see if anything is happening in your fragment that may be intensive on the resources (commenting out different pieces of code may help in investigating this).

Categories

Resources