Communication from Activity to Fragment with ViewPager

11,777

You need to set up some event listener logic where the fragment registers with the activity for the event it wants. The fragment should register on creation and unregister on destruction.

Then when the event occurs, the activity goes through the list of registered listeners and notifies them of the event.

I give a detailed example in this answer.

You can use an event bus library to simplify some of the wiring between the activity and the fragments.

Share:
11,777
Zuop
Author by

Zuop

Updated on June 13, 2022

Comments

  • Zuop
    Zuop almost 2 years

    I have one Activity and two Fragments for a tablayout containing a viewpager. Now I can communicate from the fragment to the Activity by implementing the google's guide callback interface. But how can I communicate the other way from activity to fragment? If something happens in the Activity (external events) I want to update the Fragment. I managed to get the Frag1 fragment with

    MyFragmentPagerAdapter a = (MyFragmentPagerAdapter) viewPager.getAdapter();
    Frag1 frag = (Frag1) a.getItem(0);
    

    but when I call public methods on frag I get a IllegalStateException: Fragment not attached to Activity probably because getItem(0) returns a new instance of Frag1 and this is not attached yet... is there anyone who can provide a clean solution for this whole Viewpager -> Activity to Fragment communication?

    Some code for you:

    in Activity:

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
            if (viewPager != null) {
                viewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager()));
            }
    
            TabLayout tabLayout = (TabLayout) findViewById(R.id.sliding_tabs);
            if (tabLayout != null) {
                tabLayout.setupWithViewPager(viewPager);
            }
    }
    

    activity layout:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        tools:context="com.MainActivity">
    
        <android.support.design.widget.TabLayout
            android:id="@+id/sliding_tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    
        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"/>
    </LinearLayout>
    

    MyFragmentPagerAdapter:

    public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
    
        final int PAGE_COUNT = 2;
        private String tabTitles[] = new String[] { "tab1", "tab2" };
    
        public MyFragmentPagerAdapter(FragmentManager fm) {
            super(fm);
        }
    
        @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0:
                    return Frag1.newInstance(position + 1);
                case 1:
                    return Frag2.newInstance(position + 1);
                default:
                    return null;
            }
        }
    
        @Override
        public int getCount() {
            return PAGE_COUNT;
        }
    
        @Override
        public CharSequence getPageTitle(int position) {
            return tabTitles[position];
        }
    }
    

    Frag1:

    public class Frag1 extends Fragment {
        public static final String ARG_PAGE = "ARG_PAGE";
    
        private int mPage;
    
        private onFrag1InteractionListener mListener;
    
        public Frag1() {
            // Required empty public constructor
        }
    
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @return A new instance of fragment Frag1.
         */
        public static Frag1 newInstance(int page) {
            Frag1 fragment = new Frag1();
            Bundle args = new Bundle();
            args.putInt(ARG_PAGE, page);
            fragment.setArguments(args);
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getArguments() != null) {
                mPage = getArguments().getInt(ARG_PAGE);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_1, container, false);
            return view;
        }
    
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            if (context instanceof onFrag1InteractionListener) {
                mListener = (onFrag1InteractionListener) context;
            } else {
                throw new RuntimeException(context.toString()
                        + " must implement onFrag1InteractionListener");
            }
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            mListener = null;
        }
    
        public interface onFrag1InteractionListener {
            // TODO: Update argument type and name
            void onFrag1Interaction(Action action);
        }
    
    • Nitesh
      Nitesh almost 8 years
      extend AppCompatActivity instead of Activity then check what is happening?
    • Zuop
      Zuop almost 8 years
      Activity already extends AppCompatActivity
    • Nitesh
      Nitesh almost 8 years
      What do you mean by external events? Suppose if you want to update the fragment when tabs are swiped. Like that
    • hanbumpark
      hanbumpark almost 8 years
      maybe you can find solution here. stackoverflow.com/questions/8532462/…
    • Zuop
      Zuop almost 8 years
      By external events I thought of events that are not connected with the fragments in fact not with the user, for example if a new wifi is available the activity will notice and wants to communicate this to the fragment. @Hanbum Bak this doesn't help because I have no xml fragment, just the fragment layout itself and the fragment class (Frag1 extends Fragment)
    • Raghavendra
      Raghavendra almost 8 years
      you can get which fragment which loaded currently. Otherwise I think you can use FragmentStatePagerAdapter and use getItemPosition in that adapter with POSITON_NONE. Better ref: here
    • Zuop
      Zuop almost 8 years
      how do i get the current loaded fragment? or how do i show frag1 if frag2 is showing and update a view on frag1
    • Raghavendra
      Raghavendra almost 8 years
      u have fragmentPagerAdapter reference in activity right. use adapter.getItem(viewPager.getCurrentItem()) to get the current fragment.
    • Swaminathan V
      Swaminathan V almost 8 years
      Do you want to send some data to the fragment based on some events in its parent activity. am i correct ?? @Zuop
    • Zuop
      Zuop almost 8 years
      @Raghavendra i tried that: MyFragmentPagerAdapter adapter = (MyFragmentPagerAdapter) viewPager.getAdapter(); final Frag1 frag1 = (Frag1) adapter.getItem(viewPager.getCurrentItem()); but when i call the public method of the frag1 I get the illegalstateexception at Ragu Swarminathan no i dont want to send data, i just need the reference of the fragment so that i can call a method from the fragment
    • Swaminathan V
      Swaminathan V almost 8 years
      Do you want to refresh the fragment when some events happens in its mainactivity? Does my question was correct ? @Zuop
    • Raghavendra
      Raghavendra almost 8 years
      I'm just guessing can u just comment this newInstance() method and use default constructor to get the fragment reference and try once.?
    • Zuop
      Zuop almost 8 years
      to be specific: the fragment contains a Imageview and with the method in Frag1 public void swapImage(boolean b) i want to set another imagesource if b is true.
    • Zuop
      Zuop almost 8 years
      I followed this tutorial guides.codepath.com/android/… and they used this newInstance method
  • Zuop
    Zuop almost 8 years
    I followed the instructions in the link of your answer and that solved it.. though I find it weird to implement a Interface to communicate from Fragment -> Activity and then additionally another interface to communicate from Activity -> Fragment
  • kris larson
    kris larson almost 8 years
    My example didn't implement an interface on the activity, but doing that is still good practice for unit testing and reuse. Lots of Google's examples for things like navigation drawer do this.
  • Zuop
    Zuop almost 8 years
    Oh okay.. I must have misinterpret your answer but nvm it works for now. Though you said "Define a listener interface. I usually do this as an inner interface within the activity" in the link :)
  • kris larson
    kris larson almost 8 years
    Instead of having a separate, standalone interface definition, I define the interface inside the activity, similar to an inner class. This makes sense because it's the activity that has the collection of listener impls that will receive the event callback. So the activity defines the interface, the fragments implement the interface, e.g. extends Fragment implements MyActivity.EventListener. Hopefully that makes it all a little clearer.