I have a fragment I am trying to add into a view.
FragmentManager fragMgr=getSupportFragmentManager();
feed_parser_activity content = (feed_parser_activity)fragMgr
.findFragmentById(R.id.feedContentContainer);
FragmentTransaction xaction=fragMgr.beginTransaction();
if (content == null || content.isRemoving()) {
content=new feed_parser_activity(item.getLink().toString());
xaction
.add(R.id.feedContentContainer, content)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(null)
.commit();
Log.e("Abstract", "DONE");
}
When this code is executed I get the following error in debug..
java.lang.IllegalArgumentException: No view found for id 0x7f080011
for fragment feed_parser_activity{41882f50 #2 id=0x7f080011}
feed_parser_activity is a Fragment that is set to Fragment layout in xml.
I am using a FragmentActivity to host the Fragment Layout holding the feed_parser_layout.
Am I coding this correctly above?
I was having this problem too, until I realized that I had specified the wrong layout in setContentView() of the onCreate() method of the FragmentActivity.
The id passed into FragmentTransaction.add(), in your case R.id.feedContentContainer, must be a child of the layout specified in setContentView().
You didn't show us your onCreate() method, so perhaps this is the same problem.
This error also occurs when having nested Fragments and adding them with getSupportFragmentManager() instead of getChildFragmentManager().
The solution was to use getChildFragmentManager()
instead of getFragmentManager()
when calling from a fragment. If you are calling the method from an activity, then use getFragmentManager().
That will solve the problem.
Another scenario I have met.
If you use nested fragments, say a ViewPager in a Fragment with it's pages also Fragments.
When you do Fragment transaction in the inner fragment(page of ViewPager), you will need
FragmentManager fragmentManager = getActivity().getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
getActivity() is the key here.
...
I had this problem (when building my UI in code) and it was caused by my ViewPager (that showed Fragments) not having an ID set, so I simply used pager.setID(id) and then it worked.
This page helped me figure that out.
In my case I was trying to show a DialogFragment containing a pager and this exception was thrown when the FragmentPagerAdapter attempted to add the Fragments to the pager. Based on howettl answer I guess that it was due to the Pager parent was not the view set in setContentView() in my FragmentActivity.
The only change I did to solve the problem was to create the FragmentPagerAdapter passing in a FragmentMager obtained by calling getChildFragmentManager(), not the one obtained by calling getFragmentManager() as I normally do.
public class PagerDialog extends DialogFragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.pager_dialog, container, false);
MyPagerAdapter pagerAdapter = new MyPagerAdapter(getChildFragmentManager());
ViewPager pager = (ViewPager) rootView.findViewById(R.id.pager);
pager.setAdapter(pagerAdapter);
return rootView;
}
}
This exception can also happen if the layout ID which you are passing to FragmentTransaction.replace(int ID, fragment) exists in other layouts that are being inflated. Make sure the layout ID is unique and it should work.
With Nested fragments
For me by using getChildFragmentManager() instead of getActivity().getSupportFragmentManager() resolved crash
java.lang.IllegalArgumentException: No view found for id
An answer I read on another thread similar to this one that worked for me when I had this problem involved the layout xml.
Your logcat says "No view found for id 0x7f080011".
Open up the gen->package->R.java->id and then look for id 0x7f080011.
When I had this problem, this id belonged to a FrameLayout in my activity_main.xml file.
The FrameLayout did not have an ID (there was no statement android:id = "blablabla").
Make sure that all of your components in all of your layouts have IDs, particularly the component cited in the logcat.
I got this error when I upgraded from com.android.support:support-v4:21.0.0 to com.android.support:support-v4:22.1.1.
I had to change my layout from this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container_frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
To this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/container_frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</FrameLayout>
So the layout MUST have a child view. I'm assuming they enforced this in the new library.
I know this has already been answered for one scenario, but my problem was slightly different and I thought I'd share in case anybody else is in my shoes.
I was making a transaction within onCreate(), but at this point the view tree has not been inflated so you get this same error. Putting the transaction code in onResume() made everything run fine.
So just make sure your transaction code runs after the view tree has been inflated!
I was facing a Nasty error when using Viewpager within Recycler View.
Below error I faced in a special situation.
I started a fragment which had a RecyclerView with Viewpager (using FragmentStatePagerAdapter). It worked well until I switched to different fragment on click of a Cell in RecyclerView, and then navigated back using Phone's hardware Back button and App crashed.
And what's funny about this was that I had two Viewpagers in same RecyclerView and both were about 5 cells away(other wasn't visible on screen, it was down). So initially I just applied the Solution to the first Viewpager and left other one as it is (Viewpager using Fragments).
Navigating back worked fine, when first view pager was viewable . Now when i scrolled down to the second one and then changed fragment and came back , it crashed (Same thing happened with the first one). So I had to change both the Viewpagers.
Anyway, read below to find working solution.
Crash Error below:
java.lang.IllegalArgumentException: No view found for id 0x7f0c0098 (com.kk:id/pagerDetailAndTips) for fragment ProductDetailsAndTipsFragment{189bcbce #0 id=0x7f0c0098}
Spent hours debugging it. Read this complete Thread post till the bottom applying all the solutions including making sure that I am passing childFragmentManager.
Nothing worked.
Finally instead of using FragmentStatePagerAdapter , I extended PagerAdapter and used it in Viewpager without Using fragments. I believe some where there is a BUG with nested fragments. Anyway, we have options. Read ...
Below link was very helpful :
Viewpager Without Fragments
Link may die so I am posting my implemented Solution here below:
public class ScreenSlidePagerAdapter extends PagerAdapter {
private static final String TAG = "ScreenSlidePager";
ProductDetails productDetails;
ImageView imgProductImage;
ArrayList<Imagelist> imagelists;
Context mContext;
// Constructor
public ScreenSlidePagerAdapter(Context mContext,ProductDetails productDetails) {
//super(fm);
this.mContext = mContext;
this.productDetails = productDetails;
}
// Here is where you inflate your View and instantiate each View and set their values
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup layout = (ViewGroup) inflater.inflate(R.layout.product_image_slide_cell,container,false);
imgProductImage = (ImageView) layout.findViewById(R.id.imgSlidingProductImage);
String url = null;
if (imagelists != null) {
url = imagelists.get(position).getImage();
}
// This is UniversalImageLoader Image downloader method to download and set Image onto Imageview
ImageLoader.getInstance().displayImage(url, imgProductImage, Kk.options);
// Finally add view to Viewgroup. Same as where we return our fragment in FragmentStatePagerAdapter
container.addView(layout);
return layout;
}
// Write as it is. I don't know much about it
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
/*super.destroyItem(container, position, object);*/
}
// Get the count
#Override
public int getCount() {
int size = 0;
if (productDetails != null) {
imagelists = productDetails.getImagelist();
if (imagelists != null) {
size = imagelists.size();
}
}
Log.d(TAG,"Adapter Size = "+size);
return size;
}
// Write as it is. I don't know much about it
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
Hope this was helpful !!
Just in case someone's made the same stupid mistake I did; check that you're not overwriting the activity content somewhere (i.e. look for additional calls to setContentView)
In my case, due to careless copy and pasting, I used DataBindingUtil.setContentView in my fragment, instead of DataBindingUtil.inflate, which messed up the state of the activity.
I had the same issue but my issue was happenning on orientation change. None of the other solutions worked. So it turns out that I forgot to remove setRetainInstance(true); from my fragments, when doing a two or one pane layout based on screen size.
My mistake was on the FragamentTransaction.
I was doing this t.replace(R.layout.mylayout); instead of t.replace(R.id.mylayout);
The difference is that one is the layout and the other is a reference to the layout(id)
This happens when you are calling from a fragment inside another one.
use :
getActivity().getSupportFragmentManager().beginTransaction();
I had this same issue, let me post my code so that you can all see it, and not do the same thing that I did.
#Override
protected void onResume()
{
super.onResume();
fragManager = getSupportFragmentManager();
Fragment answerPad=getDefaultAnswerPad();
setAnswerPad(answerPad);
setContentView(R.layout.abstract_test_view);
}
protected void setAnswerPad(AbstractAnswerFragment pad)
{
fragManager.beginTransaction()
.add(R.id.AnswerArea, pad, "AnswerArea")
.commit();
fragManager.executePendingTransactions();
}
Note that I was setting up fragments before I setContentView. Ooops.
This page seems to be a good central location for posting suggestions about the Fragment IllegalArgumentException. Here is one more thing you can try. This is what finally worked for me:
I had forgotten that I had a separate layout file for landscape orientation. After I added my FrameLayout container there, too, the fragment worked.
On a separate note, if you have already tried everything else suggested on this page (and the entire Internet, too) and have been pulling out your hair for hours, consider just dumping these annoying fragments and going back to a good old standard layout. (That's actually what I was in the process of doing when I finally discovered my problem.) You can still use the container concept. However, instead of filling it with a fragment, you can use the xml include tag to fill it with the same layout that you would have used in your fragment. You could do something like this in your main layout:
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include layout="#layout/former_fragment_layout" />
</FrameLayout>
where former_fragment_layout is the name of the xml layout file that you were trying to use in your fragment. See Re-using Layouts with include for more info.
I fixed this bug, I use the commitNow() replace commit().
mFragment.getChildFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment_container,fragment)
.commitNowAllowingStateLoss();
The commitNow is a sync method, the commit() method is an async method.
I use View Binding in my project and was inattentive to add setContentView() after inflating ActivityHelloWorldBinding class:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHelloWorldBinding.inflate(layoutInflater)
// Add this line.
setContentView(binding.root)
}
In my case I had a SupportMapFragment in a recycler view item (I was using the lower overhead "liteMode" which makes the map appear as non-interactive, almost like a static image). I was using the correct FragmentManager, and everything appeared to work fine... with a small list. Once the list of items exceeded the screen height by a bit then I started getting this issue when scrolling.
Turned out, it was because I was injecting a dynamic SupportMapFragment inside a view, which was inside another fragment, to get around some issues I was having when trying to declare it statically in my XML. Because of this, the fragment placeholder layout could only be replaced with the actual fragment once the view was attached to the window, i.e. visible on screen. So I had put my code for initialising the SupportMapFragment, doing the Fragment replace, and calling getMapAsync() in the onAttachedToWindow event.
What I forgot to do was ensure that my code didn't run twice. I.e. in onAttachedToWindow event, check if my dynamic SupportMapFragment was still null before trying to create a new instance of it and do a Fragment replace. When the item goes off the top of the RecyclerView, it is detached from the window, then reattached when you scroll back to it, so this event is fired multiple times.
Once I added the null check, it happened only once per RecyclerView item and issue went away! TL;DR!
This issue also happens when you don't put <include layout="#layout/your_fragment_layout"/> in your app_bar_main.xml
use childFragmentManager instead of activity!!.supportFragmentManager
I encountered this problem when I tried to replace view with my fragment in onCreateView(). Like this:
public class MyProjectListFrag extends Fragment {
private MyProjectListFragment myProjectListFragment;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
FragmentManager mFragmentManager = getFragmentManager();
myProjectListFragment = new MyProjectListFragment();
mFragmentManager
.beginTransaction()
.replace(R.id.container_for_my_pro_list,
myProjectListFragment, "myProjectListFragment")
.commit();
}
It told me
11-25 14:06:04.848: E/AndroidRuntime(26040): java.lang.IllegalArgumentException: No view found for id 0x7f05003f (com.example.myays:id/container_for_my_pro_list) for fragment MyProjectListFragment{41692f40 #2 id=0x7f05003f myProjectListFragment}
Then I fixed this issue with putting replace into onActivityCreated(). Like this:
public class MyProjectListFrag extends Fragment {
private final static String TAG = "lch";
private MyProjectListFragment myProjectListFragment;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater
.inflate(R.layout.frag_my_project_list, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
FragmentManager mFragmentManager = getFragmentManager();
myProjectListFragment = new MyProjectListFragment();
mFragmentManager
.beginTransaction()
.replace(R.id.container_for_my_pro_list,
myProjectListFragment, "myProjectListFragment")
.commit();
}
You have to return a view in onCreateView() so that you can replace it later
You can put any operation towards this view in the following function in fragment liftcycle, like onActivityCreated()
Hope this helps!
In my case this exception was thrown when I used different ids for the same layout element (fragment placeholder) while having several of them for different Build Variants. For some reason it works perfectly well when you are replacing fragment for the first time, but if you try to do it again you get this exception.
So be sure you are using the same id if you have multiple layouts for different Build Variants.
I was having this problem. In my case I have forgotten to add FrameLayout in my Xml File, after adding frame layout, my problem has been solved.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/wraper"
android:layout_above="#id/wraper"/>
If you are trying to replace a fragment within a fragment with the fragmentManager but you are not inflating the parent fragment that can cause an issue.
In BaseFragment.java OnCreateView:
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.replace(R.id.container, new DifferentFragment())
.commit();
}
return super.onCreateView(inflater, container, savedInstanceState);
Replace super.onCreateView(inflater, container, savedInstanceState);
with inflating the correct layout for the fragment:
return inflater.inflate(R.layout.base_fragment, container, false);
I've had the same problem when was doing fragment transaction while activity creation.
The core problem is what Nick has already pointed out - view tree has not been inflated yet. But his solution didn't work - the same exception in onResume, onPostCreate etc.
The solution is to add callback to container fragment to signal when it's ready:
public class MyContainerFragment extends Fragment {
public static interface Callbacks {
void onMyContainerAttached();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d(TAG, "--- onAttach");
((Callbacks) activity).onMyContainerAttached();
}
//... rest of code
}
And then in activity:
public class MainActivity extends Activity
implements MyContainerFragment.Callbacks
{
#Override
public void onMyContainerAttached() {
getFragmentManager()
.beginTransaction()
.replace(R.id.containerFrame, new MyFragment())
.commit();
}
//...
}
In my case, i was using a fragment class file to declare a listview adapter class.
I just used a different file for the public adapter class and the error was gone.
It happens also when you have two views in two fragments with the same ids
Related
I am creating a Fragment Activity with tabs. Now whenever i select a tab, corresponding fragment gets recreated and its
onCreateView()
method is called.
But what i want is to reuse the view of the fragment so that whenever a tab is selected system does not call
onCreateView()
of fragment instead it shows the previous view of the fragment if it exists.
Please reply soon.
You may want to show and hide the fragments instead of adding and removing them when selecting a tab.
For example:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (frag.isAdded()) {
transaction.show(R.id.layout, frag);
} else {
transaction.add(R.id.layout, frag);
}
transaction.commit();
setOffScreenPageLimit() method can be used to set how many fragments you want to keep alive even if they are not visible to the user.
Try this, it works for screen rotation and should work for your situation as well:
1) When you add fragment for the first time, create it with name parameter:
getSupportFragmentManager()
.beginTransaction()
.replace(<containerID>, <fragment>, YOUR_FRAGMENT_NAME_CONST)
.commit();
2) When you need to find it:
YourFragmentType fragment;
fragment = (YourFragmentType) getSupportFragmentManager()
.findFragmentByTag(YOUR_FRAGMENT_NAME_CONST);
if (fragment != null){
//TODO set data to the existing fragment
}
else{
//TODO create and initialize your fragment
}
Edit:
You should differentiate Fragment object creation and onCreateView() being called.
It's right that you should avoid unnecessary object creation and so reuse fragments.On the other hand (as for my experience) it's better to adhere Android's onCreateView() politics to guarantee consistent user experience.
If you really want to save CPU time and avoid re-inflating complicated view (and settle all issues yourselves) - you may just check it is null like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View
if (mView == null) {
mView = inflater.inflate(R.layout.your_fragment, container, false);
...
}
...
}
As the headline says: Is it possible to get the currently visible fragment with all UI elements initialized within the onCreate() method of the activity?
I am implementing a separation into model, view and controller with separate controller classes that handle business logic and UI events. Therefore they need a reference to the current fragment. These controllers are initialized in the onCreate() method of the activity hence I need the initialized fragment within that method.
I welcome any kind of advice :)
EDIT:
Adding some code for better understanding:
I'm using dagger for dependency injection and would like to do this in the onCreate() method. As I said before my controller needs an the mapView element. And that is why I would like to have a fragment with the mapView element initialized.
MapActivity#onCreate(Bundle):
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.activity_layout);
MyMapFragment fragment = new MyMapFragment();
getFragmentManager().beginTransaction()
.add(R.id.activity_container, fragment, "fragment")
.commit();
ObjectGraph.create(new Module(fragment.getMapView())).inject(this);
}
activity_layout.xml
<android.support.v4.widget.DrawerLayout>
<FrameLayout
android:id="#+id/activity_container"
... />
<ListView .../>
</android.support.v4.widget.DrawerLayout>
fragment_layout.xml
<RelativeLayout>
<org.osmdroid.map.MapView
android:id"#+id/mapview"
... />
<Button .../>
</RelativeLayout>
2ND EDIT:
So it seems like that is not possible... Yay for the downvote ^^
By default, no. At the time activity onCreate() runs, the fragment is not attached to the activity yet.
Right place to access a fragment's views is in the fragment itself. Consider putting the controller assignments in the fragment within its lifecycle such as onCreateView() or onViewCreated().
It is possible to explicitly run queued up fragment transactions using executePendingTransactions(), or implicitly after super.onStart() has been run in the activity lifecycle. After that the fragment views are accessible in the activity view hierarchy.
in your onCreate method add the following (I used a textview as an example):
while (fragment.getView() == null) {
}
rootView = fragment.getView();
TextView myView = (TextView) rootView.findViewById(R.id.text_view);
Make sure to return the rootView in your fragment's onCreateView method as follows:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_activity,
container, false);
return rootView;
}
I know that the idea of getting your fragment to access its views from the main activity is bad. Yet, this solution may give you what you want.
I've got an Activity with a DrawerLayout, using the guidelines from http://developer.android.com/training/implementing-navigation/nav-drawer.html.
When I click on an drawerItem, I replace the current view with the new fragment:
Fragment fragment;
Bundle args = new Bundle();
fragment = new NewsListFragment();
args.putInt("category", position);
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
mDrawerList.setItemChecked(position, true);
Now, sometimes the old fragment is not replaced but the new fragment is placed on top of the old one:
http://img15.imageshack.us/img15/3179/1kqj.png
Why is this, and how to solve this problem?
Relevant XML:
<!-- The main content view -->
<LinearLayout
android:id="#+id/rlMain"
android:layout_width="fill_parent"
android:orientation="vertical"
android:layout_height="fill_parent">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
</FrameLayout>
This only happens sometimes, and I haven't found a flow to reproduce this yet. The app doesn't support rotating, so it won't happen there.
We went live with this version and havent received any complaints about this, so I will assume this was the correct answer:
in your onCreateView method add:
if (container != null) {
container.removeAllViews();
}
Be sure to check if container is not null!
Thanks https://stackoverflow.com/users/2677588/lia-pronina!
After about 1 week, I found the solution without adding background color or anything else. Just add this code and fix that bullshit. I hope it will help all of you.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
container.clearDisappearingChildren();
return inflater.inflate(R.layout.fragment, container, false);
}
Add in the every layout
android:background="#FFFFFF"
The layouts background in default are transparen, so just put a background color and the new elements fragment, not display over old fragment
let's try with
fragmentManager.beginTransaction().executePendingTransactions();
after you call commit() method
I run in this same problem and I see that there's already an accepted answer but these answer is not 100% right and didn't fix my problem.
The proposed answer by #Niels removes the views but the fragment(s) is(are) still added.
This is what I am using:
/**
* Call this to remove all the other added fragments and keep only the current one.
*
* #param activity the activity to which the fragment has been attached.
* #param fragment the fragment we want to keep.
*/
public static void removeOtherAddedFragments(#NonNull AppCompatActivity activity, #NonNull Fragment fragment) {
FragmentManager fragmentManager = activity.getSupportFragmentManager();
for (Fragment frag : fragmentManager.getFragments()) {
if (frag != null && !frag.equals(fragment) && frag.isAdded()) {
fragmentManager.beginTransaction().remove(frag).commit();
}
}
}
I am calling this in my onResume to be sure that it will be called also when I navigate back to the fragment.
I also encounter this issue I found that we are doing a wrong when replacing fragment
private void changeFragment(Fragment targetFragment){
assert getFragmentManager() != null;
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, targetFragment, "fragment")
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
this method is replace fragment as other people's code. the reason why fragments are display each other is we define different ID in framelayout in xml. we have to define framelayout IDs(old fragment and new fragment) same.
old framelayout and new framelayout ID in xml for above code should be
<FrameLayout
android:layout_width="match_parent"
android:id="#+id/main_fragment"
android:layout_height="match_parent"/>
Let's say that you have the root fragment A which you add to a container. Next you add fragment B and you set addToBackStack in the transaction. Lastly you add fragment C but you omit addToBackStack. Now when you press the back button you get these fragments on top of each other.
To recap:
Fragment A is added
Fragment B replaces A with addToBackStack
Fragment C replaces B without addToBackStack
Pressing back results in weird overlaid fragments where A lies on top of C.
You could solve this issue by also adding C to the backStack.
I have a Fragment (I'll call it pagerFragment) that is added to the backstack and is visible. It holds a viewPager with a FragmentPagerAdapter. The FragmentPagerAdapter holds (let's say) two fragments: A and B.
First adding of the fragments works great.
Fragment A has a button that once clicked, adds a fragment (C) to the backstack.
The problem is this: if I add that fragment (C), and then click back, the pagerAdapter is empty, and I cannot see any fragments inside.
If I use a hack, and destroy the children fragments (A and B) in the pagerFragments onDestroyView(), this solves the problem, although I don't wan't to use this hack.
Any ideas what the issue could be?
I had the same problem. The solution for me was simple:
in onCreateView I had:
// Create the adapter that will return a fragment for each of the three
// primary sections of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getActivity()
.getSupportFragmentManager());
where SectionPageAdapter is something like this:
class SectionsPagerAdapter extends FragmentPagerAdapter {
...
}
after changing getSupportFragmentManager to
mSectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager());
it started working!
It sounds like you are using nested fragments since your ViewPager is inside a PagerFragment. Have you passed getChildFragmentManager() to the constructor of your FragmentPagerAdapter? If not you should.
I don't think you need a FragmentStatePagerAdapter, but I would give that a shot since it handles saving and restoring Fragment state. The fact that your onDestroyView() hack works makes me think that you may want a FragmentStatePagerAdapter.
It could also have something to do with the way the FragmentPagerAdapter adds Fragments. The FragmentPagerAdapter doesn't add Fragments to the backstack. Imagine if you had a 10+ pages added in your ViewPager and the user swiped through them. The user would need to hit back 11 times just to back out of the app.
It may also be related to this post: Nested Fragments and The Back Stack.
Also I'm not sure what you are adding the Fragment C to. Are you adding it to the same container as the ViewPager?
Well at least you have a few options to investigate. In these situations I like to debug down into the Android SDK source code and see what's causing the behaviour. I recommend grabbing the AOSP source and adding frameworks/support and frameworks/base as your SDK sources. That's the only true way to understand what is happening and avoid making random changes until things work.
Use getChildFragmentManager() instead of getSupportFragmentManager().
It will work fine.
I just faced the problem in our project as well. The root cause is the way the the FragmentPagerAdapter works:
The FragmentPagerAdapter just detaches a Fragment he does not currently need from its View but does not remove it from its FragmentManager. When he wants to display the Fragment again he looks if the FragmentManager still contains the Fragment using a tag that is created from the view id of the ViewPager and the id returned by the adapters getItemId(position) call. If he finds a Fragment he just schedules an attach of the Fragment to its View within the updating transaction of the FragmentManager. Only if he does not find a Fragment this way he creates a new one using the adapters getItem(position) call!
The problem with a Fragment containing a ViewPager with a FragmentPagerAdapter is, that the contents of the FragmentManager is never cleaned up when the containing Fragment is put to the back stack. If the containing Fragment comes back from the back stack it creates a new View but the FragmentManager still contains the fragments that were attached to the old view and the attach of an existing fragment does not work anymore.
The easiest way to get rid of this problem is to avoid nested fragments. :)
The second easiest way is as already mentioned in other posts to use the ChildFragmentManager for the FragmentPagerAdapter as this one gets properly updated during the life cycle of the container fragment.
As there are projects (as my current one) where both options are not possible, I have published here a solution that works with an arbitrary FragmentManager by using the hashCode of the sub fragments as the item id of the fragment at that position. It comes at the price of storing all fragments for all positions within the adapter.
public class MyPagerAdapter extends FragmentPagerAdapter {
private static int COUNT = ...;
private final FragmentManager fragmentManager;
private Fragment[] subFragments = new Fragment[COUNT];
private FragmentTransaction cleanupTransaction;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
this.fragmentManager = fragmentManager;
}
#Override
public Fragment getItem(int position) {
return getSubFragmentAtPosition(position);
}
#Override
public int getCount() {
return COUNT;
}
#Override
public long getItemId(int position) {
return getSubFragmentAtPosition(position).hashCode();
}
//The next three methods are needed to remove fragments no longer used from the fragment manager
#Override
public void startUpdate(ViewGroup container) {
super.startUpdate(container);
cleanupTransaction = fragmentManager.beginTransaction();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
cleanupTransaction.remove((Fragment) object);
}
#Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
cleanupTransaction.commit();
}
private Fragment getSubFragmentAtPosition(int position){
if (subFragments[position] == null){
subFragments[position] = ...;
}
return subFragments[position];
}
}
I had same problem, just set adapter twice at once and that's all.
Example code :
private fun displayImg(photo1:String, photo2:String){
val pager:ViewPager = v?.findViewById(R.id.ProductImgPager)!!
val arr = ArrayList<String>()
arr.add(photo1)
arr.add(photo2)
pager.adapter = AdapterImageView(fm, arr ,arr.size)
pager.adapter = AdapterImageView(fm, arr ,arr.size)
}
I have a problem reloading an activity with tabs and fragments when I change the orientation of my device.
Here's the situation:
I have an activity which has 3 tabs in the action bar. Each tab loads a different fragment in a FrameLayout in main view. Everything works fine if I don't change the orientation of the device. But when I do that Android tries to initialize the currently selected fragment twice which produce the following error:
E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment
Here's the sequence of steps that produce the error:
I load the activity, select tab nr 2. and change the orientation of the device.
Android destroys the activity and the instance of the fragment loaded by tab nr 2 (from now on, 'Fragment 2'). Then it proceeds to create new instances of the activity and the fragment.
Inside Activity.onCreate() I add the first tab to the action bar. When I do that, this tab gets automatically selected. It may represent a problem in the future, but I don't mind about that now. onTabSelected gets called and a new instance of the first fragment is created and loaded (see code below).
I add all the other tabs without any event being triggered, which is fine.
I call ActionBar.selectTab(myTab) to select Tab nr 2.
onTabUnselected() gets called for the first tab, and then onTabSelected() for the second tab. This sequence replaces the current fragment for an instance of Fragment 2 (see code below).
Next, Fragment.onCreateView() is called on Fragment 2 instance and the fragment layout gets inflated.
Here is the problem. Android Calls onCreate() and then onCreateView() on the fragment instance ONCE AGAIN, which produces the exception when I try to inflate (a second time) the layout.
Obviously the problem is Android is initializing the fragment twice, but I don't know why.
I tried NOT selecting the second tab when I reaload the activity but the second fragment gets initialized anyway and it is not shown (since I didn't select its tab).
I found this question: Android Fragments recreated on orientation change
The user asks basically the same I do, but I don't like the chosen answer (it's only a workaroud). There must be some way to get this working without the android:configChanges trick.
In case it's not clear, what I want to know how whether to prevent the recreation of the fragment or to avoid the double initialization of it. It would be nice to know why is this happening also. :P
Here is the relevant code:
public class MyActivity extends Activity implements ActionBar.TabListener {
private static final String TAG_FRAGMENT_1 = "frag1";
private static final String TAG_FRAGMENT_2 = "frag2";
private static final String TAG_FRAGMENT_3 = "frag3";
Fragment frag1;
Fragment frag2;
Fragment frag3;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// my_layout contains a FragmentLayout inside
setContentView(R.layout.my_layout);
// Get a reference to the fragments created automatically by Android
// when reloading the activity
FragmentManager fm = getFragmentManager();
this.frag1 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_1);
this.frag2 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_2);
this.frag3 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_3)
ActionBar actionBar = getActionBar();
// snip...
// This triggers onTabSelected for the first tab
actionBar.addTab(actionBar.newTab()
.setText("Tab1").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_1));
actionBar.addTab(actionBar.newTab()
.setText("Tab2").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_2));
actionBar.addTab(actionBar.newTab()
.setText("Tab3").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_3));
Tab t = null;
// here I get a reference to the tab that must be selected
// snip...
// This triggers onTabUnselected/onTabSelected
ab.selectTab(t);
}
#Override
protected void onDestroy() {
// Not sure if this is necessary
this.frag1 = null;
this.frag2 = null;
this.frag3 = null;
super.onDestroy();
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
if (curFrag == null) {
curFrag = createFragmentInstanceForTag(tab.getTag().toString());
if(curFrag == null) {
// snip...
return;
}
}
ft.replace(R.id.fragment_container, curFrag, tab.getTag().toString());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft)
{
Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
if (curFrag == null) {
// snip...
return;
}
ft.remove(curFrag);
}
private Fragment getFragmentInstanceForTag(String tag)
{
// Returns this.frag1, this.frag2 or this.frag3
// depending on which tag was passed as parameter
}
private Fragment createFragmentInstanceForTag(String tag)
{
// Returns a new instance of the fragment requested by tag
// and assigns it to this.frag1, this.frag2 or this.frag3
}
}
The code for the Fragment is irrelevant, it just returns an inflated view on onCreateView() method override.
I got a simple answer for that:
Just add setRetainInstance(true); to the Fragment's onAttach(Activity activity) or onActivityCreated(Bundle savedInstanceState).
These two are call-backs in the Fragment Class.
So basically, what setRetainInstance(true) does is:
It maintains the state of your fragment as it is, when it goes through:
onPause();
onStop();
It maintains the instance of the Fragment no matter what the Activity goes through.
The problem with it could be, if there are too many Fragments, it may put a strain on the System.
Hope it helps.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
setRetainInstance(true);
}
Open for Correction as always. Regards, Edward Quixote.
It seems that, when the screen is rotated and the app restarted, it is recreating each Fragment by calling the default constructor for the Fragment's class.
I have encountered the same issue and used the following workaround:
in the fragment's onCreateView begining of:
if (mView != null) {
// Log.w(TAG, "Fragment initialized again");
((ViewGroup) mView.getParent()).removeView(mView);
return mView;
}
// normal onCreateView
mView = inflater.inflate(R.layout...)
I think this is a fool proof way to avoid re-inflating of the root view of the fragment:
private WeakReference<View> mRootView;
private LayoutInflater mInflater;
/**
* inflate the fragment layout , or use a previous one if already stored <br/>
* WARNING: do not use in any function other than onCreateView
* */
private View inflateRootView() {
View rootView = mRootView == null ? null : mRootView.get();
if (rootView != null) {
final ViewParent parent = rootView.getParent();
if (parent != null && parent instanceof ViewGroup)
((ViewGroup) parent).removeView(rootView);
return rootView;
}
rootView = mFadingHelper.createView(mInflater);
mRootView = new WeakReference<View>(rootView);
return rootView;
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
mInflater=inflater!=null?inflater:LayoutInflater.from(getActivity());
final View view = inflateRootView();
... //update your data on the views if needed
}
add
android:configChanges="orientation|screenSize"
in the manifest file
To protect activity recreate try to add configChanges in your Activity tag (in manifest), like:
android:configChanges="keyboardHidden|orientation|screenSize"
My code was a little different, but I believe our problem is the same.
In the onTabSelected I didn't use replace, I use add when is the first time creating the fragment and attach if isn't. In the onTabUnselected I use detach.
The problem is that when the view is destroyed, my Fragment was attached to the FragmentManager and never destroyed. To solve that I implemented on the onSaveInstanceBundle to detach the fragment from the FragmentManager.
The code was something like that:
FragmentTransition ft = getSupportFragmentManager().begin();
ft.detach(myFragment);
ft.commit();
In the first try I put that code in the onDestroy, but I get a exception telling me that I couldn't do it after the onSaveInstanceBundle, so I moved the code to the onSaveInstanceBundle and everything worked.
Sorry but the place where I work don't allow me to put the code here on StackOverflow. This is what I remember from the code. Feel free to edit the answer to add the code.
I think you are facing what I faced. I had a thread downloader for json which starts in onCreate() , each time I changed the orientation the thread is called and download is fired. I fixed this using onSaveInstance() and onRestoreInstance() to pass the json response in a list, in combination of checking if the list is not empty, so the extra download is not needed.
I hope this gives you a hint.
I solved this problem by using below code.
private void loadFragment(){
LogUtil.l(TAG,"loadFragment",true);
fm = getSupportFragmentManager();
Fragment hf = fm.findFragmentByTag("HOME");
Fragment sf = fm.findFragmentByTag("SETTING");
if(hf==null) {
homeFragment = getHomeFragment();// new HomeFragment();
settingsFragment = getSettingsFragment();// new Fragment();
fm.beginTransaction().add(R.id.fm_place, settingsFragment, "SETTING").hide(settingsFragment).commit();
fm.beginTransaction().add(R.id.fm_place, homeFragment, "HOME").commit();
activeFragment = homeFragment;
}else{
homeFragment = hf;
settingsFragment = sf;
activeFragment = sf;
}
}
Initiate this method in OnCreate();