I am trying to understand a bad behaviour in fragments: the onCreateView and onActivityCreated methods are called even the fragment is not 'visible' in the layout.
If you use the code:
TestFragment testFragment = new TestFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.fragmentDetail, testFragment, "test");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
replacing the FrameLayout with id fragmentDetail with the fragment and then you rotate the device, the fragments method are still invoked even if the container is not present anymore in the portrait layout. This doesn't happen if you use the 'static' <fragment> tag.
If you use the static fragment, the fragments methods are invoked just when the fragment appears. Is it possible to achieve the same behaviour without using the fragment tag? I need a way to avoid the rendering of the fragment if it is not in the layout.
Thanks
I have found one fix to this. It is slightly different from the suggested Handling orientation changes with Fragments one:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (!fragment.isInLayout() && container == null) return null;
...
}
In this way you can avoid the case when the fragment is statically put into the layout (in that case the container is null but the method isInLayout() returns true.
By the way it is still weird to me this behaviour.
AFAIK, fragments work almost as Activities. They have the same lifecycle. http://developer.android.com/reference/android/app/Fragment.html#Lifecycle So, if you don't have references to them, it won't make them close. They are referenced by the system and live by themselves. So, you should finish them somehow.
Related
I have a FragmentStatePagerAdapter that shows 3 tabs and of which, the 1st tab (fragment) commits a FragmentTransaction inside it's onCreateView(). For some users, I am seeing an IllegalStateException which is caused by commit() state loss.
After reading up on a few stack overflow Q&As and a blog post by Alex Lockwood on Fragment Transactions and Activity State Loss, I better understand what is causing the state loss and I can take several approaches.
I can move my fragment commit to onPostResume.
I can move my fragment commit to onCreate() - probably safest.
However, there is a lot of logic inside onCreateView() and if I were to take the above approach, I may introduce regression bugs. I am wondering, since onCreate() will eventually call onCreateView(), if it is okay to just check savedInstanceState for null. Would this solve the problem?
Here is my current code:
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
FFragment frag = new FFragment();
frag.setDateSelected(mDateSelected);
getFragmentManager().beginTransaction()
.add(R.id.container, frag, FRAG_TAG)
.commit();
...
}
What I imagine would fix the state loss:
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
if(savedInstanceState == null) {
frag = new FFragment();
frag.setDateSelected(mDateSelected);
getFragmentManager().beginTransaction()
.add(R.id.container, frag, FRAG_TAG)
.commit();
} else {
frag = (FFragment) getSupportFragmentManager()
.findFragmentByTag(FRAG_TAG);
}
...
}
Why am I asking this and not just implementing and testing it? I've tried to kill the activity through ADB and DDMS but was not able to reproduce the state loss - thus asking if this approach would work. Or, do I have to do the inevitable and move the commit to onCreate or onPostResume()?
Is there another way to test trigger state loss?
There are a couple of things you got wrong
You are using getFragmentManager() and getSupportFragmentManager() inside a fragment, you should alwasy use getChildFragmentManager() when using nested fragments
OnResumeFragments() and onCreate that Alex talked about are in respect to activity and not fragments, there is not such method inside a framgent
The solution you are proposing is for an entirely different thing, it is used when the activity is recreated from the backstack
The solution would be to move your code to onViewCreated() method of your fragment and use childFragmentManager, I am using a viewpager inside a fragment and I initialize it inside onViewCreated(), haven't faced any such problem till now
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.
I added some Fragment into a TableLayout and I want to manage them from my container Activity, so I used this:
Fragment fragment = (Fragment) tableLayout.getChildAt(i);
but getChildAt(int) returns a View and a View could NOT cast to Fragment
I don't understand why people are down-voting your question. Fragments can be very confusing at times, especially for beginners. To understand your problem, you must learn what is a Fragment and how they are used.
To start with, a View is something that has an existence on the screen. Examples include: TextView, EditText, Button, etc. They are placed inside "layouts" written in Xml or Java/Kotlin. These layouts are shown using an Activity.
Now, a Fragment is not a View. It does not have any existence on the screen at all. Instead, it's a class that simply manages a "layout" — kinda similar to an Activity. If you need the View returned by your Fragment's onCreateView(), you can directly use findViewById() within your Activity.
If you need a reference to your Fragment, there are two possible ways of doing this:
1) If you added the Fragment programmatically like this
getFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container_viewgroup, myFragment, FRAGMENT_TAG)
.commit();
You can use:
MyFragment myFragment = (MyFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
2) If you added the Fragment inside an XML layout like this:
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/fragmentContainer"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
You can use this:
getFragmentManager().findFragmentById(R.id.fragmentContainer);
Basically, each Activity has a FragmentManager class that maintains all the active Fragments, and there are two ways of finding them: Using a unique TAG that you pass while showing a fragment, or passing the container view-ID where the fragment was added.
For people looking how to actually get a reference to the Fragment object from a View there is now a method in FragmentManager called findFragment(View) (reference)
//in Java
FragmentManager.findFragment(view)
//in Kotlin there is an extension function
view.findFragment()
Be careful - it will throw an IllegalStateException if the view was not added via a fragments onCreateView.
You can not get a fragment like this. You will have to add fragment with a tag and retrieve it by that tag.
to add a fragment do following:
getFragmentManager().beginTransaction().add(R.id.container, fragment, "tagTofindFragment");
to get fragment:
fragment = getFragmentManager().findFragmentByTag("tagTofindFragment");
Here tagTofindFragment is that tag that should be unique among your fragments.
I am having problems adding a fragment to a frame layout placeholder:
TestDialogFragment newFragment = new TestDialogFragment();
int id = R.id.fragment_placeholder; //id of the FrameLayout placeholder for the TestDialogFragment
getFragmentManager().beginTransaction().add(id, newFragment, "testdialogfragment").commit();
I have breakpoints in the onAttach and onCreate methods in TestDialogFragment and they don't get hit, which goes against what I read in the android fragment API Guide. Is there something I'm doing wrong with the add transaction above?
P.S. TestDialogFragment extends Fragment
I fixed this by setting & getting arguments with Bundles instead of interacting directly with the fragment via its public methods after committing the transaction. My mistake was assuming that the fragment was up and ready to use (attached, layout inflated etc) as soon as the commit() statement was finished.
I am adding layouts to my Project and each time I add a layout it also add another layout that comes with the Word "fragment"... can somebody explain me for what is for? I had look over the web and it explain other kind of fragments...
Android Studio, when asked to create an Activity, will create 4 things for you :
An Activity class
a layout file for the Activity class, which will include a FrameLayout serving as the container to place the fragment
a Fragment class (created as an innner class inside your Activity)
a layout file for your fragment (this is the second layout you see in
your project structure), say for example fragment_test.xml
If you look closely, you Activity code will contain something like this :
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_test, container, false);
return rootView;
}
}
My guess it that this was done so it guides developers to use fragments for the screen's actual content rather than placing it inside the Activity's layout itself. The Fragment is designed to be a re-usable component, so if you have several layouts for your activity depending on the screen size/orientation, you can re-use the same fragments, just placing them differently inside your Activity layout, which is an excellent practice.
I hope I clarified things a bit ;)
This is an Android 0.8 question, using the Activity with Fragment template.
So, how would you go about swapping one fragment in for a second fragment? in the same Frame? Perhaps for a button click, for example.
Use case, might be a "connect the dots" questionnaire where the next button goes to the next fragment.
I understand that the answer is FragmentManager and FragmentTransactions.
When I do this from with in a click event,
FragmentManager FM = getFragmentManager();
FragmentTransaction FT = FM.beginTransaction();
FT.replace(R.id.container, new FRAG02());
FT.addToBackStack(null);
FT.commit();
I get an error:
must implement OnFragmentInteractionListener
It would seem that there is a SOP way of replacing fragments that I am not aware of. Seems like a related comment.