IllegalArgumentException: No view found for id for fragment --- ViewPager in ViewPager
Asked Answered
I

11

27

I've met the problem that trouble me for days.

There is a ViewPager in the main activity which holds 3 Fragments as tab fragments. In the first fragment there is a ListView which holds some views, and which is the most important, another ViewPager. I want to hold some photos in the sub ViewPager, and use some more fragments here.

Now there is the trouble:
When the first Fragment is stopped (The third fragment in the parent ViewPager is seen on screen) and resumed (the user switch to the second fragment), the app crashes and the debugger says:

java.lang.IllegalArgumentException: No view found for id 0x7f05008b (com.example.viewpager:id/sub_viewpager) for fragment ScreenSlidePageFragment

I've already use the getChildFragmentManager() as this is a situation of nested fragments.

Here is the key code of list adapter corresponding to the first fragment in the parent ViewPager:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    int type = getItemViewType(position);
    switch (type) {
        case TYPE_BANNER:
            if (convertView == null) {
                convertView = mBannerView.getBannerView(parent);
            }
            mBannerView.update(convertView);
            break;
        case TYPE_ITEM:
            break;
    }
    return convertView;
}

Here is the code of mBannerView:

public class BannerView {

    private static final DisplayImageOptions IMAGE_OPTIONS_SCALE_STRETCHED =
            new DisplayImageOptions.Builder()
                    .cacheInMemory()
                    .cacheOnDisc()
                    .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
                    .build();

    private FragmentActivity mActivity;
    private Fragment mFragment;
    private List<Banner> mBanners;
    private ScreenSlidePagerAdapter mPagerAdapter;
    private ViewPager mViewPager;

    public BannerView(FragmentActivity activity, Fragment fragment) {
        mActivity = activity;
        mFragment = fragment;
    }

    public void update(View convertView) {
        mViewPager = (ViewPager) convertView;
        if (mBanners != null && !mBanners.isEmpty()) {
            if (mPagerAdapter == null) {
                mPagerAdapter = new ScreenSlidePagerAdapter(mFragment.getChildFragmentManager());
                mViewPager.setAdapter(mPagerAdapter);
            }
        }
        mViewPager.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnBannerClickListener != null) {
                    mOnBannerClickListener.onBannerClick();
                }
            }
        });
    }

    class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return new ScreenSlidePageFragment(mBanners.get(position).getImageUrl());
        }

        @Override
        public int getCount() {
            return mBanners == null ? 0 : mBanners.size();
        }
    }

    class ScreenSlidePageFragment extends Fragment {

        private String mUrl;

        ScreenSlidePageFragment(String url) {
            super();
            mUrl = url;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.item_banner, container, false);
            if (view != null) {
                ImageView imageView = (ImageView) view.findViewById(R.id.item_banner_image);
                imageView.setLayoutParams(new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
                ImageLoader.getInstance().displayImage(mUrl, imageView, IMAGE_OPTIONS_SCALE_STRETCHED);
            }
            return view;
        }
    }
}

Here is the detailed error list:

11-10 18:12:19.217    1444-1444/? E/MessageQueue-JNI﹕ java.lang.IllegalArgumentException: No view found for id 0x7f05008b (com.example.viewpager:id/sub_viewpager) for fragment ScreenSlidePageFragment{428d8ea0 #0 id=0x7f05008b}
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:919)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1086)
        at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1884)
        at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:1514)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:947)
        at android.support.v4.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1280)
        at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:672)
        at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1467)
        at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:472)
        at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:141)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1068)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:914)
        at android.support.v4.view.ViewPager$3.run(ViewPager.java:244)
        at android.support.v4.view.ViewPager.completeScroll(ViewPager.java:1761)
        at android.support.v4.view.ViewPager.onInterceptTouchEvent(ViewPager.java:1896)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1854)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1912)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1912)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2211)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1912)
        at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2228)
        at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1471)
        at android.app.Activity.dispatchTouchEvent(Activity.java:2424)
        at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2176)
        at android.view.View.dispatchPointerEvent(View.java:7571)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:3883)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3778)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3379)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3429)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3398)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3483)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3406)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3540)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3379)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3429)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3398)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3406)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3379)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5419)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5399)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5370)
        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5493)
        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:182)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:132)
        at android.os.Looper.loop(Looper.java:124)
        at android.app.ActivityThread.main(ActivityThread.java:5289)
        at java.lang
Ikeda answered 10/11, 2013 at 10:19 Comment(2)
post relevant snippet of codeMarquetry
@blackbelt Codes have been added.Ikeda
I
53

Update:

I've read the source code of the FragmentManager and finally got the true reason: This exception happens when the fragments want to be attached to the viewpager before the viewpager is attached to its parent. In other words, before the getView() method returns, the fragments are inflated. Then the findViewById() method of the container of the ViewPager is called but the ViewPager is in detached state yet, so null is found and the IllegalArgumentException is thrown.

The solution is to create a custom ViewPager and lazy set the adapter:

public class BannerViewPager extends ViewPager {
    PagerAdapter mPagerAdapter;

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mPagerAdapter != null) {
            super.setAdapter(mPagerAdapter);
            mPageIndicator.setViewPager(this);
        }
    }

    @Override
    public void setAdapter(PagerAdapter adapter) {
    }

    public void storeAdapter(PagerAdapter pagerAdapter) {
        mPagerAdapter = pagerAdapter;
    }

    public BannerViewPager(Context context) {
        super(context);
    }

    public BannerViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}

And in the getView() method, use storeAdapter() instead of setAdapter.

The following statements are not correct. The words above are the actual reason.


Finally I've got the answer. It consists of two parts.

  1. In the parent ViewPager I used a FragmentPagerAdapter to hold fragments, but now I use a FragmentStatePagerAdapter instead. The difference between these two can be found here: Difference between FragmentPagerAdapter and FragmentStatePagerAdapter.
    Simply speaking, FragmentPagerAdapter will store more information when a fragment is stopped. In this situation, the first fragment in the parent ViewPager is stopped but not destroyed while the views in this fragment are destroyed. After resumed, the fragment try to re-inflate all the views. But before the getView() method is called and the sub-ViewPager is recreated, the child FragmentManager tries to find the sub-ViewPager to hold those previously stored fragments. Thus the "java.lang.IllegalArgumentException: No view found for id" occurs.

  2. After I replaced the FragmentPagerAdapter with FragmentStatePagerAdapter, another problem appears. the sub-viewpager is missing when the parent fragment (the first fragment in the parent viewpager) was stopped, destroyed and resumed. This happens when the first fragment is choosen, soon afterwards the third fragment is choosen, and at last the first fragment is re-choosen.
    I think this is a bug of android sdk. Inspired by here and here, I use some tricky methods to solve the problem. The point is, when a parent fragment is destroyed, the field member --- mChildFragmentManager "ends up with a broken internal state" and is not totally cleaned up. When the parent fragment is recreated, mChildFragmentManager is not null, but the sub fragments is already been destroyed after the parent fragment being destroyed, which was managered by mChildFragmentManager. Thus the sub ViewPager displays an empty view on the screen, which responds to a fake fragment which is actually not existing. The funny thing is, after swiping right on the sub ViewPager for several times, the sub fragments and the views appear again.

Here is the codes:

Parent Adapter:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = getBannerView(mParent);
    }
    mViewPager = (ViewPager) convertView;
    if (mBanners != null && !mBanners.isEmpty()) {
        if (mPagerAdapter == null) {
            FragmentManager childFM = mFragment.getChildFragmentManager();
            removeOldFragment(childFM);
            mPagerAdapter = new ScreenSlidePagerAdapter(childFM, mBanners);
            mViewPager.setAdapter(mPagerAdapter);
        }
    }
    return convertView;
}

The key method:

    private void removeOldFragment(FragmentManager fm) {
        try {
            Field added = fm.getClass().getDeclaredField("mAdded");
            added.setAccessible(true);
            added.set(fm, null);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        try {
            Field active = fm.getClass().getDeclaredField("mActive");
            active.setAccessible(true);
            active.set(fm, null);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
Ikeda answered 11/11, 2013 at 6:32 Comment(9)
This seems to be working for me :) But I am unsure why we have to create a new method instead of doing the same work in the overridden setAdapter()?Duluth
I'm a noob with this, but where do I get the variable mPageIndicator from? It is located in your custom ViewPager in onAttachedToWindow()Infinitesimal
@occorled I think the reason we create a new method is because we don't want the setAdapter() method to fire until the ViewPager is attached properly to the parent. So we store the PageAdapter via the new method storeAdapter() to be used when the ViewPager is attached. We know it is attached once onAttachedToWindow() is called; thus, once onAttachedToWindow() is called we call setAdapter() inside of that method and use the saved PageAdapter.Infinitesimal
@IsaacL mPageIndicator is a reference which holds an instance of the ViewPagerIndicator. See viewpagerindicator.com for more infomation.Ikeda
@Ikeda Could you take a look at this question about ViewPager? Thank you #27937750Wesle
I believe calling super.setAdapter(null) in onDetachedFromWindow is also needed for this to work. I have a ViewPager inside a tab Fragment that's one of a number of tabs contained within another ViewPager, and my app would crash even on using the lazy store adapter method on the inner ViewPager. Adding super.setAdapter(null) in onDetachedFromWindow does seem to fix this.Gaskill
Great original answer which was very nearly there for me, but using @DhruvKapoor method of setting adapter to null onDetachedFromWindow made it work. Thanks both of you!Fukuoka
YES - this totally made my day !Effusion
I have the same problem, but the solution doesn't help me.Becka
O
8

Xieyi did a good job of explaining the reasoning behind the exception.

Here's the solution I found:

@Override
public void onPause() {
    super.onPause();

    for ( Fragment f : getChildFragmentManager().getFragments() ) {
        if ( f instanceof MyFragmentType ) {
            getChildFragmentManager().beginTransaction().remove( f ).commit();
        }
    }

}

I was getting the exception whenever I went to a new fragment and came back. Make sure to remove the fragments in your "cleaup" method, then you can load can reload your views when you come back to the fragment.

Oakleil answered 18/2, 2017 at 3:53 Comment(2)
where can i call this methodMarchall
I added this on my fragment which initiated my viewPager. So there, i checked if f instanceof myViewPager to remove it, and everything worked :D Really thanks for this!!Starfish
T
8

If your ViewPager has been on the Fragment, then use not getFragmentManager but getChildFragmentManager.

Torchbearer answered 17/9, 2018 at 15:2 Comment(0)
B
6

When you have an Activity and a Fragment that would be contained by the Activity, you will have layout-xml with an id where the fragmet should be placed.

If the id is not located in the Activity you will get this exception.

Example:

getSupportFragmentManager().beginTransaction()
            .add(R.id.banner_container, bannerFragment)
            .commit();

if R.id.banner_container is not an id in the Activity-layout-xml you will get the exception.

Borneo answered 4/3, 2014 at 18:37 Comment(0)
L
6

Ran into the same problem when using nested ViewPagers. I fixed the problem by replacing both the FragmentPagerAdapters with FragmentStatePagerAdapters.

Lagrange answered 22/7, 2015 at 19:40 Comment(1)
Replaced with what?Marthmartha
D
5

Although Xieyi's solution works perfectly fine, it's pretty much based on doing a reflection, which isn't really a good thing to do.

As an alternative answer, I managed to remove the child fragments using fragment transaction instead, which I believe should be the preferred approach in here.

First, register a fragment lifecycle callback to child fragment manager so that we can keep reference to the child fragments.

private List<Fragment> mFragments = new ArrayList<>();

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    …(DO YOUR STUFF)
    //register a fragment lifecycle callback
    getChildFragmentManager().registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {

        @Override
        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
            super.onFragmentCreated(fm, f, savedInstanceState);
            mFragments.add(f);
        }

        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
            super.onFragmentDestroyed(fm, f);
            mFragments.remove(f);
        }
    }, false);

Next, we only need to iterate over the fragments list to remove them from the child fragment manager, inside onCreateView:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                         @Nullable Bundle savedInstanceState) {
    //remove child fragments before inflating view
    Iterator<Fragment> iterator = mFragments.iterator();
    while (iterator.hasNext()) {
        Fragment fragment = iterator.next();
        iterator.remove();
        getChildFragmentManager().beginTransaction()
                .remove(fragment)
                .commitNow();
    }
    return inflater.inflate(R.layout.your_fragment, container, false);
}

That's it.

Defrost answered 30/3, 2017 at 8:35 Comment(0)
S
3

I had a similar problem. I have a fragment (HomeFragment) with a ListView and a ViewPager which has some fragments inside, everything works great when the fragment starts, when I click in some row in the ListView, it will take me to another fragment, but when I want to go back to the previously fragment (HomeFragment) I get the No view found for id error (I have addToBackStack the fragment).

The solution was to verify in the onCreateView of HomeFragment if the View and other components were already been created, like:

View view;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
  if (view == null) {
     //All the code in the onCreateView
     view = inflater.inflate(R.layout.item_banner, container, false);
     //...
  }
}

With this verification when it comes back to the previously fragment everything works fine, the View, pager, list, etc were already created, so there is no need to create it again. =)

Structuralism answered 16/4, 2015 at 15:4 Comment(1)
Works perfect! My problem was this: Fragment1 (ViewPager inside) -> change to Fragment2 -> if I returned to Fragment1 the screen was blank! Now with your check everything works fine! I am NOT using getChildFragmentManager!Swindle
A
1

I have the same error with similar context, but the selected answer from Xieyi doesn't work. I have to delay the setting of the adapter to the next frame.

 viewPager.post(() -> viewPager.setAdapter(adapter));
Adiaphorism answered 4/2, 2020 at 10:31 Comment(0)
S
0

I solved this by avoiding retaining the instance in the creation of the view with setRetainInstance(true). You can comment the line or put it in false

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    setRetainInstance(false);
    return inflater.inflate(R.layout.fragment, container, false);
}
Sidewinder answered 19/6, 2019 at 14:17 Comment(0)
C
0

I got a similar problem. In my situation, it is a RecyclerView that holds a ViewPager. Then, when the ViewPager is detached from RecyclerView and the container of the RecycleView first change its state to stop then change to resume. The FragmentManager of it will try to restore fragments it added. However at this time, the ViewPager is not attached to its parent. Thus FragmentManager throws a Exception.

Here is the source code:

ViewGroup container = null;
    if (f.mContainerId != 0) {
        if (f.mContainerId == -1) {
            this.throwException(new IllegalArgumentException("Cannot create fragment " + f + " for a container view with no id"));
        }

        container = (ViewGroup) this.mContainer.onFindViewById(f.mContainerId);
        if (container == null && !f.mRestored) {
            String resName;
            try {
                resName = f.getResources().getResourceName(f.mContainerId);
            } catch (NotFoundException var9) {
                resName = "unknown";
            }

            this.throwException(new IllegalArgumentException("No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + resName + ") for fragment " + f));
        }
    }

My solution is detach ViewPager's child Fragments when the ViewPager is detaching from RecycleView.

Here is an example:

 @Override
public void postUnBindView(BaseCell cell) {
    super.postUnBindView(cell);
    if (vpFragmentManager != null && mPagerAdapter != null && mPagerAdapter.curFragments != null) {
        FragmentTransaction transaction = vpFragmentManager.beginTransaction();
        for (GuessLikePagerFragment guessLikePagerFragment : mPagerAdapter.curFragments.values()) {
            transaction.detach(guessLikePagerFragment);
        }
        transaction.commit();
    }
}

Hope it helps.

Craiova answered 19/2, 2020 at 1:10 Comment(0)
K
-2

I had the same problem before, and I tried the accepted answer but it didn't work for me. After more research I found my solution.

This is in my fragment

CustomViewPager customViewPager = (customViewPager) View.inflate(parentActivity, R.layout.custom_viewpager, null);

And this is my layout

<com.test.package.ui.CustomViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Kerrikerrie answered 25/4, 2016 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.