FragmentPagerAdapter with ViewPager and two Fragments. Go to the first from the second and update first's text
Asked Answered
R

3

8

I'm not familiar with FragmentPagerAdapter, so this is going to be one of those questions that we (you) read the description critically.

Structure: I have a FragmentPagerAdapter (code below), that will hold two fragments at a time. The first displays book excerpts, and the second a list of book titles.

Goal: I want to achieve what is described in the title: the user can navigate to the second fragment in the pager, click on a title, and then I want to move the user back to the first fragment and tell the first fragment to update the text. The first fragment has a triggerRefresh method for that.

Code: I believe my problem happens because of the way FragmentPagerAdapter reuses/creates the Fragments (which I don't understand). This is my class:

static class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public Fragment getItem(int position) {
        switch(position) {
        case 0:
            return new ExcerptsFragment();
        case 1:
            return new BookListFragment();
        default:
            throw new IllegalArgumentException("not this many fragments: " + position);
        }
    }
}

This is how I created the relevant members:

ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
MyFragmentPagerAdapter mFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mFragmentPagerAdapter);

And this is what I've tried elsewhere in my Activity, when I receive the callback from the book titles Fragment with the title selected:

mViewPager.setCurrentItem(0); // back to excerpts screen page. It's OK.
// Here's the problem! How to identify the fragment 0 
// to ExcerptsFragment and call its triggerRefresh()?!?

Series of problems:

Calling the adapter's getView() won't work because it will return a new instance of ExcerptsFragment, which is not the one currently attached (as expected, throws the exception).

I've seen many people here (example) just storing fragments in the getView(). Is that right? Because by looking at the official examples, seems like an anti-pattern to me (defeat the automatic reference by holding the items). And that is also the opinion here and here (and looks right to me).

Any suggestions? I wouldn't be surprised if I'm not understanding all of this one bit...

Rigi answered 5/4, 2012 at 3:10 Comment(0)
R
7

Disclaimer: Although this had worked perfectly fine for me before, you should be aware of the classic pitfalls of depending on internal, private behavior. While I wrote tests that would eventually warn me if the internal implementation changed, I have since moved on to greener pastures. And you should, too. As such, the value of this question and its answer is only historical, in my opinion.


Sorry about that question, I think it was the hour.

To solve that problem, I implemented this solution as is. Seems to work just fine. So, I believe it was just a matter of finding the (currently attached) fragment instance by figuring out how its Id is named. The link above explains how it's made.

I opted to answer my own question instead of deleting it because I believe novices like me on these pagers will benefit from a "real case scenario". Most of the answers I've seen talk most about the theory, which is the right way BTW... but without a real example to work on sometimes people like me get lost.

Anyway, here is the last piece of code that I needed (the commented part above):

int n = 0;
mViewPager.setCurrentItem(n); // in the question I had stopped here.

ExcerptsFragment f = (ExcerptsFragment) ContainerActivity.this
        .getSupportFragmentManager().findFragmentByTag(getFragmentTag(n));
f.triggerRefresh();

// ... below the helper method: used the solution from the link.

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

So, I'm having a feeling that this is a robust solution, because I'm not holding any references to fragments (thus risking the references being outdated). I kept my own code at a minimum, therefore minimizing the chances of me doing something stupid.

Of course, if you have something to add, to show us, to tell what is wrong in doing it or what can be improved, I'll be glad to hear from you.

Rigi answered 5/4, 2012 at 4:23 Comment(5)
you should not rely on compatibility with internal mechanism of assigning tags. See my answer to similar question how to do this properly: #14035590Benoite
@Benoite Thank you for warning me. I included a disclaimer to warn potential readers.Rigi
you are welcome :) I think however it would be better to provide a link directly to my answer (not only to that question) as my answer is very new and thus still low scored so readers may just stuck with one of the 2 top scored answers, both of which have problems. (I added some explanation to my answer why the solution that overrides instantiateItem may be not enough on its own)Benoite
Sorry, I'll have to pass. I never link to—or otherwise endorse, directly or indirectly—solutions I (still) didn't use, test or confirm (as said, I've moved on from the “solution” (quotes) in my answer for quite some time now). As a personal comment, I suggest you do not worry about upvotes and visibility. If there is one thing I noticed in these years on SO is that users will find the best answer and upvote them, if they have problems and the solution is better, solving their problems. Do not worry about this. If the other answers have problems, let the user be his own judge of that. ;-)Rigi
I don't really care about my UIP (useless internet points), but about ppl being mislead by high scores of wrong answers, but whatever ;)Benoite
P
2

I searched for a solution to this problem a while myself. Your approach in principle works, but it will break your code if ever the code of the fragment tag creation in the Android base class implementation changes. This is a quite nasty dependency!

A more elegant approach would be to turn the problem around and keep an instance of your base activity in your fragment. Implement a setter for the tag in your activity and call that inside the fragment upon creation - the tag there is simply available with getTag().

An example implementation can be found here.

Papilloma answered 7/9, 2013 at 14:10 Comment(1)
Best solution for this very random problem so far. However two things: you can get a reference to the "parent activity" with getActivity(). And you need to keep in mind that you need to keep the tag- names through config changes, because even though the adapter might be recreated, the fragments are probably not (and thus the tags will not be set again)Leprechaun
K
0

I solved this problem by using WeakReferences to the fragments upon creation. See : https://mcmap.net/q/57286/-how-to-get-existing-fragments-when-using-fragmentpageradapter

If you find anything wrong with this approach, please comment.

Krohn answered 24/5, 2014 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.