I have a thread which triggers a callback method in my Activity which has a fragment. The Activity then calls a method within the Fragment to refresh the UI with new info. In that method, getString() is called. When getstring() is called, I get a 'Fragment not Attached to Activity error'. This UI refreshing error is guarded runOnUIThread. The fragment is added like so:
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction()
.add(fragmentContainerID, fragment, tag)
.commit();
fm.executePendingTransactions();
The activity uses two different layout files for horizontal and vertical orientation. Their are two fragment containers in both layouts. They have the same names in both horizontal and vertical layouts, but they do not match each other. The fragments are added using the above code the first time the Activity is created. On orientation changes, the fragments are automatically added into the layouts because the fragment containers have the same ids in both vertical and horizontal orientation.
When the savedInstanceState is not null, I get a reference to the fragments by using
fm.findFragmentByTag
Can anyone please tell me what I am doing wrong ? The fragments are visibly added to the Activity perfectly fine, and several minutes go by before this thread callback occurs, so I don't see how the Fragment is not "attached".
EDIT: Sigh, after some debugging Ive found that adding fragments in this way is perfectly fine. Code elsewhere in the Activity ( I registered for a callback twice) caused my fragment not attached error. If anyone reading this is getting this error, keep in mind that this issue can be triggered from elsewhere. Also, you have to setRetainInstance to true in the fragment to avoid it being recreated. Tags alone don't do that.
Fragments are added one-per-container so if you are using .add(fragmentContainerID, fragment, tag) ensure firstly that fragmentContainerID is different for each of the fragments added.
However, these sorts of issue are most often caused by a misunderstanding in how orientation changes are handled. When you change orientation and let the oncreate handle the adding: you get a new fragment of the same type what this means is that if your background task has a reference to your fragment - it has a reference to the old (now detached) fragment. What you need to do is to make sure the same fragment gets added to the tag (not a new instance). To do that just don't (re)create a fragment instance when we are only restoring state
public class MyActivity ... {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
MyFragment frag = new MyFragment();
frag.setArguments( getIntent().getExtras() ); //if it takes params
fm.beginTransaction().add(fragmentContainerID, frag, tag).commit();
}
//after this point a call to MyFragment frag = (MyFragment) fm.findFragmentByTag(tag); should always return the correct fragment
}
It's probably also a wise idea for your task to look for the fragment right when it does its callback to update the UI (and NOT to hold on to a reference to it). Depending on how you've set up your task it just means changing from FragmentManager fm = getSupportFragmentManager(); then the task is created, to calling it as part of the onPostExecute
When change orientation, fragment automatically added to FragmentManager and forget own tag!!
Use this code to find your fragment:
FragmentManager fragmentManager= getSupportFragmentManager();
for (Fragment fragment : fragmentManager.getFragments())
{
if(fragment instanceof MyFragment)
myFragment = (MyFragment)fragment;
}
After you find the fragments on orientation change, try detaching them and then reattaching them before any operation. Or if that does not work remove them and add them. then continue..... I have even had to sometimes put a time delay of a few millisec after detach and before attach to ensure all OK.
Related
I have 2 fragments. My first fragment have button which leads me to second fragment. It has this code:
binding.btnGet5Days.setOnClickListener {
val forecastFragment = ForecastFrag()
val transaction: FragmentTransaction =
parentFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, forecastFragment)
transaction.addToBackStack(null)
transaction.commit()
}
In my MainActivity i have this code:
val cityFragment = CityFrag()
val fm: FragmentManager = supportFragmentManager
fm.beginTransaction()
.add(R.id.fragment_container, cityFragment)
.commit()
Fragments are in FragmentContainer,
The problem is when i'm joining second fragment through this button and turn my phone into landscape mode, my first fragment layering to my second fragment. How can i solve it? :)
It's hard to know without seeing your full code, but it's possible the code you posted from MainActivity is adding a fragment on top of the existing stack. When you rotate the device, the Activity is destroyed and recreated, but the FragmentManager maintains its state so you don't lose everything. If your recreated activity code always adds a new fragment instance, you'll end up with what was already there, plus another CityFrag on top
The official recommendation is to use the Jetpack Navigation library, which will handle all that for you. If you don't want to go that far right now, you'll have to do your own checking and creation logic.
One thing you can do is to check if the savedInstanceState Bundle passed into your activity's onCreate is null - if it is, then this is a fresh start, and you can initialise with your first fragment. If it's not null, then your app is being recreated from some saved state, so you should probably let the FragmentManager take care of restoring itself and its back stack.
Otherwise take a look at FragmentManager - there's a bunch of useful methods like getBackStackEntryCount, findFragmentByTag etc. that you can use to work out what state your fragments are in, and if you need to add one or not. Depends on your code!
I am using nested fragments in the bottom navigation. at the first time, all fragments were loaded and everything is fine but when I changed the fragment, null exception fired and I saw that getParentFragment() return null since onDetach get called I need to know that in child fragment what happens in that func? child fragment gets detach from its parent and I have to set it again? how to avoid this detaching?
public void switchFrag() {
Fragment displayedFragment;
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
if (isListFragmentDisplayed) {
displayedFragment = mapFragment;
transaction.replace(R.id.fragment_contaner, displayedFragment, "map").commit();
} else {
displayedFragment = listFragment;
transaction.replace(R.id.fragment_contaner, displayedFragment, "list").commit();
}
isListFragmentDisplayed = !isListFragmentDisplayed;
}
The detach method removes the fragment from the UI, but its state is maintained by the Fragment Manager. This means you can reuse this fragment by calling the attach method, with a modified ViewHierarchy
Nested fragments are supported as of Android 4.2.
you can use getChildFragmentManager() ...
Detach:
Detach the given fragment from the UI. This is the same state as when
it is put on the back stack: the fragment is removed from the UI,
however its state is still being actively managed by the fragment
manager. When going into this state its view hierarchy is destroyed.
I am rewriting a Bluetooth app with 3 Activities to use just 1 Activity and 3 Fragments:
So I have now 4 files:
MainActivity.java (contains bluetooth and shared preferences code)
MainFragment.java (contains ellipsis menu to show SettingsFragment)
SettingsFragment.java (contains "scan" button to show ScanningFragment)
ScanningFragment.java (displays nearby bluetooth devices in a list)
It almost works, but as an Android programming newbie I don't understand - what to do with Fragments when I show some other Fragment?
Should I just drop the Fragments (and remove from FragmentManager?) to be garbage collected?
Or should I add these 3 private variables to MainActivity.java and reuse them (when the user navigates forwards and backwards)?
private MainFragment mMainFragment;
private SettingsFragment mSettingsFragment;
private ScanningFragment mScanningFragment;
Or does FragmentManager somehow manage all 3 Fragment for me - regardless if they are visible or not?
Here is my current code (it is simple, I just call replace() all the time)-
public class MainActivity extends Activity implements
MainListener,
SettingsListener,
ScanningListener,
BleWrapperUiCallbacks {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Fragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "settings")
.commit();
break;
}
return super.onOptionsItemSelected(item);
}
// implementing SettingsFragment.SettingsListener interface
public void scanClicked() {
// TODO how to stop indicator when returned?
setProgressBarIndeterminateVisibility(true);
String address = // get from shared preferences
Fragment fragment = ScanningFragment.newInstance(address);
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "scan")
.commit();
}
Should I just drop the Fragments (and remove from FragmentManager?) to
be garbage collected?
No need to do anything else. FragmentManager is the guy in charge of Fragments' lifecycle. Once you call replace(), FragmentManager takes care for the rest. If needed it will keep fragment in memory, or release it.
Or should I add these 3 private variables to MainActivity.java and
reuse them (when the user navigates forwards and backwards)?
No, don't do it because of the said above.
Or does FragmentManager somehow manage all 3 Fragment for me -
regardless if they are visible or not?
Yes, it does. For instance, if you have invisible retained fragment, it's enough to create it once, and FragmentManager will take care of it and will keep it even when activity gets re-created during configuration change.
If you create fragments dynamically (as far as I can see, this is your case) then I suggest to add very first fragment dynamically too. You can do it like this.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
if (savedInstanceState == null) { // <- important
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
}
This will ensure you don't duplicate MainFragment on configuration change, because when savedInstanceState is not null, then FragmentManager keeps instance of your fragment already.
Since you are calling .replace() on the fragment manager it's essentially the same thing as calling .remove(). According to the docs:
This is essentially the same as calling remove(Fragment) for all currently
added fragments that were added with the same containerViewId and
then add(int, Fragment, String) with the same arguments given here.
So you don't need to worry about any further management since it will be taken care of for you (and be removed to free up resources). This basically means that when one is shown the other is removed. If you were to call .add() then the fragments would still be alive in the background using up resources but you don't have to worry about that since using .replace() only allows one to live at a time.
If I understand your question correctly, you need not call any method to destroy the fragments after using them. Android OS will take of them. According to the documentation, When you replace the fragment with another, the onStop() method of the fragment will be executed, and documentaed as,
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.
So the fragment will be killed by the OS when the activity is killed. Till the activity is live, fragment objects will reside in the memory.
EDT:
So, if you want to use the fragment again in future, as document suggests,
Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated(). For more information about saving state, see the Activities document.
Fragment are hard coded in xml can not replaced....
but when you call replace(..) then what happen ??
Ok just consider you have 3 fragment A,B,C . In primary stage A initialize in MainActivity... now you are going to call B from A using replace(....). that means A will go onPause(),onStop() state of lifecycle and B will be initialized and paced in MainActivity...same as for C then B is onPause() ,onStop() state. If you want to reuse A or B then you need to call again A or B from C using replace (..). and then A or B will reinitialize and C goes to onPause(),onStop(). But there is another way you can do this
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
hide(A);
show(B);
ft.commit();
if you use above code block then A still be in the running state of its lifecycle but not visible (just detached from UI ).. so it would be better if you need those fragment again use hide()or show() method because it is less expensive operation then reinitialize the fragment.
I have a support library fragment that sends a network call, and recreate() the parent activity when it receives a specific network response. I can see that the activity does get recreated as I can see the onCreate() and onDestroy() are called.
But after the activity is recreated, the fragment is still there and it got stuck in a loop which keep recreating and making new fragments.
Here's part of the onCreate() of the activity:
if (someLogic()) {
fragmentA = new FragmentA();
FragmentUtil.addFragment(getSupportFragmentManager(), fragmentA);
} else {
fragmentB = new FragmentB();
FragmentUtil.addFragment(getSupportFragmentManager(), fragmentB);
}
FragmentA is the one that does the network call, and FragmentB is the fragment that should be displayed after the recreate(). When I check the list of fragments with getSupportFragmentManager().getFragments() I see 1 instances of FragmentA, and 16 instances of FragmentB.
My question is why does this happen, and how do I fix it?
Calling recreate() essentially causes the Activity to go through a configuration change. A configuration change causes both the Activity and its Fragments to be destroyed and recreated. So your Fragments should in fact be destroyed when this occurs (you can see for yourself by adding a Log message in your fragment's onDestroy method).
The fact that configuration changes cause fragments to be destroyed and created however does not mean that the FragmentManager will simply forget the fragments ever existed. The FragmentManager will still hold a reference to the newly created fragment after the configuration change occurs.
One thing you can do to prevent multiple fragments from being created is by doing something like this in your activity's onCreate():
if (savedInstanceState == null) {
// The Activity is being created for the first time, so create and
// add new fragments.
} else {
// Otherwise, the activity is coming back after being destroyed.
// The FragmentManager will restore the old Fragments so we don't
// need to create any new ones here.
}
I had same situation as #spy wrote, recreating the Activity after Day/Night mode is changed. Activity had four Fragments which they get restored after Activity.recreate() is called, because FragmentManager saves them as #Alex Lockwood stated. But restoring and not adding Fragments after recreation does not help me because of some architectural reasons of project, i wanted them to disappear.
Solution is to delete Fragments from FragmentManager just before Activity.recreate()is called, in my case it is in Activity.onStart():
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragmentA = fragmentManager.findFragmentByTag("fragmentATag");
if(fragmentA != null)
fragmentTransaction.remove(fragment);
// Find and delete other Fragments
fragmentTransaction.commit(); // or commitAllowingStateLoss()
recreate();
This way you will not get Fragments restored when Activity.onCreate() is called.
So I have enabled the setting to destroy actvities when you navigate away from an activity
Settings=>Developer Options=>Don't Keep activites
This should basically replicate an activity or fragment getting garbaged collected and then I have to restore the data via the bundle savedinstancestate.
So I understand how that works. But it seems when I navigate from fragment 1 to fragment 2 and then put the application in the background and then in the foreground(destroying the activity)
Both fragment 1 and fragment 2 show at the same time. In which only fragment 2 should be showing.
I do not know if this is something standard that I have to manage hiding and showing fragments onsavedinstance. Or if something in my code is breaking things. Below is how I push fragments which I hope is helpful:
public void pushFragmentWithAnimation(FragmentManager fm, int parentId, Fragment currentFrag, Fragment newFrag, int animEntry, int animExit) {
hideSoftKeyboard(currentFrag.getActivity());
FragmentTransaction ft = fm.beginTransaction();
// See: http://developer.android.com/reference/android/app/FragmentTransaction.html#setCustomAnimations(int, int, int, int)
ft.setCustomAnimations(animEntry, animExit, animEntry, animExit);
ft.add(parentId, newFrag, String.format("Entry%d", fm.getBackStackEntryCount())).hide(currentFrag).show(newFrag);
ft.addToBackStack(null);
ft.commit();
}
Fragment 1 is still in the backstack because when I press back I only see fragment 1. Let me know if you know why this is happening.
The lifecycle of XML added Fragments and programmatically added Fragments differ enough to make mixing them a bad idea, as explained in detail here.
The easiest way around this is to make all fragments programmatically added by replacing your XML inflated Fragment with a FrameLayout of the same ID, then in your onCreate add
FragmentManager fragMgr = getSupportFragmentManager();
if (null == fragMgr.findFragmentByTag(FRAG_TAG))
{
fragMgr.beginTransaction().
add(R.id.fragment, new Fragment1(), FRAG_TAG).commit();
}
Where FRAG_TAG is any unique string. This ensures that Fragment1 is only created if it is not already in the layout.
I am not entirely sure why this solution works. I assume its related to if the activity gets killed that it does not keep track of which fragment is currently shown and shows all of the fragments. So basically I needed to replace:
ft.add(parentId, newFrag, String.format("Entry%d", fm.getBackStackEntryCount())).hide(currentFrag).show(newFrag);
with
ft.replace(parentId, newFrag, tag);
Then when I create the initial fragment in the main activity. I only would do that when
if(savedInstanceState==null){
My updated code is below: https://github.com/CorradoDev/FragmentTest/tree/2c53f9f42e835da768f61b0233f3ab5b3adf2448