0

I created a ViewPager that uses individual Fragments. There are 3, here is an example of one of them:

public class PainFragment extends Fragment {

    private TextView mTxtScale;
    private Button mBtnMinus;
    private Button mBtnPlus;
    private int mScale;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_pain, container, false);

        mTxtScale = (TextView)v.findViewById(R.id.scale);
        mBtnMinus = (Button)v.findViewById(R.id.minus);
        mBtnPlus = (Button)v.findViewById(R.id.plus);
        mScale = Integer.valueOf(mTxtScale.getText().toString());

        mBtnMinus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mScale--;
                if(mScale == -1) {
                    mScale = 9;
                }
                mTxtScale.setText(String.valueOf(mScale));
            }
        });

        mBtnPlus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mScale++;
                if(mScale == 10) {
                    mScale = 0;
                }
                mTxtScale.setText(String.valueOf(mScale));
            }
        });


        return v;
    }

    public static PainFragment newInstance(String text) {

        PainFragment f = new PainFragment();
        Bundle b = new Bundle();
        //b.putString("msg", text);

        f.setArguments(b);

        return f;
    }

    public int getScale() {

        int scale = Integer.valueOf(mTxtScale.getText().toString());
        return scale;
    }

And I instantiated the ViewPager in my MainFragment:

public class MainFragment extends Fragment {

    Entry mEntry = new Entry();
    ViewPager mPager;
    JournalPagerAdapter mAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_main_screen, container, false);

        mPager = (ViewPager)rootView.findViewById(R.id.pager);
        mPager.setOffscreenPageLimit(2); // So all 3 pages are loaded at once.
        mAdapter = new JournalPagerAdapter(getActivity().getSupportFragmentManager());

        mPager.setAdapter(mAdapter);
        ...

I have button click listeners in the ViewPager Fragments. I would like to know the best way to set up a listener so that my main fragment can detect when a button is pressed on one of the ViewPager fragments.

/** Update - Here is my adapter class **/

public class JournalPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();

    public JournalPagerAdapter(FragmentManager mgr) {
        super(mgr);
    }
    @Override
    public Fragment getItem(int pos) {
        switch(pos) {

            case 0: return PainFragment.newInstance("PainFragment");
            case 1: return StressFragment.newInstance("StressFragment");
            case 2: return SleepFragment.newInstance("SleepFragment");
            default: return PainFragment.newInstance("PainFragment");
        }
    }

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

    /* Thanks to Streets of Boston (http://stackoverflow.com/questions/8785221/retrieve-a-fragment-from-a-viewpager)
     * for the next 3 methods, should include in all PagerAdapters. Let's you get the fragment instances by position */

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredFragments.put(position, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    public Fragment getRegisteredFragment(int position) {
        return registeredFragments.get(position);
    }

}
jwBurnside
  • 849
  • 4
  • 31
  • 66
  • 2
    you could make MainFragment implement OnClickListener then pass that into the fragment that has the button. Did you try that? – jiduvah Apr 07 '14 at 14:48
  • I've used listeners in the past when communicating between two fragments, or a fragment and host activity, I'm not sure how to apply it in this case where the fragment is hosted by the ViewPager. Do you have an example? – jwBurnside Apr 07 '14 at 15:08
  • I have no example, but you could pass the listener in once you instantiate the fragment in your adapter. – jiduvah Apr 07 '14 at 15:14
  • I think the issue is that I'm trying to pass a message from a ViewPager fragment to my "hosting" fragment, MainFragment, which is itself hosted by MainActivity. But the listener/callback examples I am familiar with are for Fragment to Activity. I don't really want to have any listener logic in my MainActivity if at all possible. – jwBurnside Apr 07 '14 at 16:05
  • you don't need to have the listener logic there. You just need to set the listener. An alternative way would be to send intents – jiduvah Apr 07 '14 at 16:15
  • That's what I was referring to, I don't want to set the listener in my MainActivity. I'll see what I can do with sending intents, I'm assuming you're referring to broadcasts? – jwBurnside Apr 07 '14 at 16:26
  • The listener really doesn't have to be in that activity at all – jiduvah Apr 07 '14 at 16:40
  • This is all helpful info, but an example would really benefit me. I included both my ViewPager fragment and my MainFragment in my question. Can you add in a simple listener between the two as an answer? I'd be glad to accept it if it leads to a solution. Thanks. – jwBurnside Apr 07 '14 at 16:49
  • yeah it probably would have been easier. Sorry for that. Looks like you have the answer now – jiduvah Apr 08 '14 at 00:20

2 Answers2

1

The proper way to achieve this is probably through the use of a callback. Your fragment would utilize a normal click listener which would then use a callback to communicate back to the hosting Activity.

See the Android docs regarding communicating from a Fragment back to the Activity; then the Activity can communicate it to other Fragments.

http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity

In some cases, you might need a fragment to share events with the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.

Robert Nekic
  • 3,087
  • 3
  • 24
  • 36
  • I'm familiar with communicating from one fragment to another, or fragment to a host activity, using callbacks. But in this case, I have several fragments hosted by the ViewPager. Don't I need a listener in the viewPager as well? Or can I communicate directly from the ViewPager fragments with the host activity (MainFragment)? – jwBurnside Apr 07 '14 at 15:40
  • 1
    Yes, the fragments in your ViewPager can communicate directly to the Activity hosting the ViewPager. I actually use a similar configuration in my app. I use a ViewPager to present a sequence of steps to the user. Each step is a fragment in the ViewPager. Each fragment in my ViewPager uses a callback to the Activity. My callback accepts a Bundle so I can freely pass almost anything back to the Activity. The callback code in the Activity looks at the Bundle and uses it to determine the next step in the flow and manipulates the ViewPager and/or one of the other fragments. – Robert Nekic Apr 07 '14 at 17:05
  • But my ViewPager is instantiated in MainFragment (which is hosted by MainActivity). I was trying to keep my logic inside MainFragment. I really don't have any flow to control, I just need to alert MainFragment that one of the ViewPager fragment's buttons have been pressed. Is this possible, or do I have to route the listener message through MainActivity in order to alert MainFragment? – jwBurnside Apr 07 '14 at 17:11
  • 1
    Well, the callback could be a fairly basic thing that simply calls a public method on your MainFragment. Otherwise, as jiduvah suggests, you could uses intents. Your fragments could simply broadcast an intent and your MainFragment would need a BroadcastReceiver to listen for the specific intent. – Robert Nekic Apr 07 '14 at 17:16
  • 1
    Judivah's suggestion of implementing the OnClickListener interface in MainFragment is also doable. Although you'd have to pass it in to your other fragment constructors or some such thing. If it were me, I'd use the callback or the OnClickListener interface on the MainFragment before going the broadcast intent route. – Robert Nekic Apr 07 '14 at 17:21
  • That sounds like what I'd like to do as well, but I'm running into problems where an example would be very helpful. If I define an interface in PainFragment, I have to capture the interface implementation through onAttach(), which has an Activity parameter. I need to pass in MainFragment, instead of the MainActivity. – jwBurnside Apr 07 '14 at 17:41
  • Ok, thanks for that written answer. I've run into another issue, however, when I'm creating a new instance of the fragment in my adapter (which I just posted as well), I get an error: method newInstance in class PainFragment cannot be applied to given types. It mentions the OnClickListener as a missing parameter. Do I have to supply this to the adapter as well? – jwBurnside Apr 07 '14 at 18:26
  • 1
    Yes, I modified the line where you make a new JournalPagerAdapter. Note "this" as the second parameter in the constructor. On your Adapter constructor you'll need to add a second parameter for the incoming OnClickListener. Stash in a private member to use when you create each PainFragment. – Robert Nekic Apr 07 '14 at 18:30
  • I'd hate to ask, you've been very helpful, but can you post the adapter changes as well? I'm getting a null pointer on that listener parameter. – jwBurnside Apr 07 '14 at 18:52
  • 1
    Sure, there you go. Apologies for the single snippet of partial classes. Chrome was fighting me a bit as I tried to make individual code blocks. – Robert Nekic Apr 07 '14 at 19:04
  • No problem, glad I could help. It's been a slow day here. :) Kudos to Judivah for originally suggesting the OnClickListener in MainFragment answer. – Robert Nekic Apr 07 '14 at 19:17
  • @RobertNekic can u please help me how can i write code for buttons i have 3 buttons one is for share , email ,save buttons they will be used for each image of viewpager i am unable to get current image from viewpager and to send over to these buttons functionality – user3233280 May 28 '14 at 16:53
1

Second answer with pseudo example of using a ClickListener instead of the Callback. This should let you keep all logic out of the Activity.

Implement OnClickListener interface in MainFragment. Add a OnClickListener to your JournalPagerAdapter constructor. Presumably the Adapter is creating the PainFragments. Add OnClickListener to PainFragment newInstance and have the Adapter provide it when it creates each PainFragment.

public class PainFragment extends Fragment {

private TextView mTxtScale;
private Button mBtnMinus;
private Button mBtnPlus;
private int mScale;
protected OnClickListener mainClickListener;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_pain, container, false);

    mTxtScale = (TextView)v.findViewById(R.id.scale);
    mBtnMinus = (Button)v.findViewById(R.id.minus);
    mBtnPlus = (Button)v.findViewById(R.id.plus);
    mScale = Integer.valueOf(mTxtScale.getText().toString());

    mBtnMinus.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mScale--;
            if(mScale == -1) {
                mScale = 9;
            }
            mTxtScale.setText(String.valueOf(mScale));
            mainClickListener.onClick(view);
        }
    });

    mBtnPlus.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mScale++;
            if(mScale == 10) {
                mScale = 0;
            }
            mTxtScale.setText(String.valueOf(mScale));
            mainClickListener.onClick(view);
        }
    });


    return v;
}

public static PainFragment newInstance(String text, OnClickListener onClickListener) {

    PainFragment f = new PainFragment();
    f.mainClickListener = onClickListener;

    Bundle b = new Bundle();        
    //b.putString("msg", text);

    f.setArguments(b);

    return f;
}

public int getScale() {



public class MainFragment extends Fragment implements OnClickListener {

Entry mEntry = new Entry();
ViewPager mPager;
JournalPagerAdapter mAdapter;

    @Override
public void onClick(View v) 
{
    // TODO Auto-generated method stub
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_main_screen, container, false);

    mPager = (ViewPager)rootView.findViewById(R.id.pager);
    mPager.setOffscreenPageLimit(2); // So all 3 pages are loaded at once.
    mAdapter = new JournalPagerAdapter(getActivity().getSupportFragmentManager(), this);

    mPager.setAdapter(mAdapter);
    ...



public class JournalPagerAdapter extends FragmentPagerAdapter {

SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();

private OnClickListener mOnClickListener;

public JournalPagerAdapter(FragmentManager mgr, OnClickListener onClickListener) {
    super(mgr);
    mOnClickListener = onClickListener;
}
@Override
public Fragment getItem(int pos) {
    switch(pos) {

        case 0: return PainFragment.newInstance("PainFragment", mOnClickListener);
        case 1: return StressFragment.newInstance("StressFragment", mOnClickListener);
        case 2: return SleepFragment.newInstance("SleepFragment", mOnClickListener);
        default: return PainFragment.newInstance("PainFragment", mOnClickListener);
    }
}

...
Robert Nekic
  • 3,087
  • 3
  • 24
  • 36