Now days usage of tabs in apps, have become a necessity. Being an android developer I have implemented them in many Android apps. But I feel, till date most of the developers don’t know how to implement them correctly. As most of the implementations would make your app crash in corner cases. Therefore in this tutorial I plan to discuss a very specific problem which is encountered while implementing tabs in android. That is, how to perform communication between an activity and fragment, two fragments, and a fragment and an activity while using tabs. Although if you to wish to simply learn – how implement tabs in Android please use this link. Since this tutorial also addresses a specific problem of Android Activity To Fragment Communication. If you have implemented tabs in android, I am sure you must have encountered this sort of problem. Therefore here in this example we would make a PagerAdapter, get its current fragment and would try to perform communication in both directions.
To perform an Android activity to fragment communication, the standard suggested way is to get the fragment instance and call public methods of that fragment directly. But when implementing tabs in Android, things are not that simple. Being a developer the first challenge I faced was; getting an instance of the already instantiated fragment in the PagerAdapter
. As the standard FragmentStatePagerAdapter
class of android does not provide any methods to retrieve the already instantiated instance of the fragment in discussion. Therefore before jumping on to; how to pass data in a fragment from activity, lets make a PagerAdapter and get its current fragment.
Android PagerAdapter: How to get the current fragment?
As I mentioned to pass data from an activity to a fragment in case of tabs we need to make the PagerAdapter
in a special way. To do so lets start by implementing tabs in android by adding the design support library in the dependencies section of the build.gradle
of your app:
compile 'com.android.support:design:23.1.1'
Please Note: Full source code is present at the GitHub. Link at the bottom of page.
Next lets define a PagerAdapter
where we would define a method by the name of getFragment()
, which would be used to return an instance of the Fragment class. This instance could be further casted into our local fragment type. In order to make this implementation work properly we may need to override some more methods of the FragmentStatePagerAdapter/FragmentPagerAdapter class. Please have a look at the code sample below:
package com.truiton.activitytofragment; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.util.SparseArray; import android.view.ViewGroup; import java.lang.ref.WeakReference; import java.util.ArrayList; public class PagerAdapter extends FragmentStatePagerAdapter { private final SparseArray<WeakReference<Fragment>> instantiatedFragments = new SparseArray<>(); private ArrayList<String> mTabHeader; public PagerAdapter(FragmentManager fm, ArrayList<String> tabHeader) { super(fm); this.mTabHeader = tabHeader; } @Override public Fragment getItem(int position) { switch (position) { case 0: TabFragment1 tab1 = new TabFragment1(); return tab1; case 1: TabFragment2 tab2 = new TabFragment2(); return tab2; default: return null; } } @Override public int getCount() { return mTabHeader.size(); } @Override public Object instantiateItem(final ViewGroup container, final int position) { final Fragment fragment = (Fragment) super.instantiateItem(container, position); instantiatedFragments.put(position, new WeakReference<>(fragment)); return fragment; } @Override public void destroyItem(final ViewGroup container, final int position, final Object object) { instantiatedFragments.remove(position); super.destroyItem(container, position, object); } @Nullable public Fragment getFragment(final int position) { final WeakReference<Fragment> wr = instantiatedFragments.get(position); if (wr != null) { return wr.get(); } else { return null; } } @Override public CharSequence getPageTitle(int position) { return mTabHeader.get(position); } }
As you can see in the above piece of code, we have implemented two extra methods instantiateItem()
and destroyItem()
to support the implementation of getFragment()
method. Also a
SparseArray with WeakReference of Fragment has been used. This allows the fragments to be garbage collected if they are destroyed, preventing a memory leak. To understand how to get a fragment from PagerAdapter, lets have a closer look at the PagerAdapter
above. The instantiateItem()
method of the FragmentStatePagerAdapter
is responsible for creation of a Fragment instance at the current position. This instance of the fragment is created through the help of getItem()
method of the adapter. By doing this process we created a SparseArray
of fragments in the instantiateItem()
. Which would be used to get the fragment from ViewPager. Now that we have successfully retrieved an instance of fragment from PagerAdapter we need complete the cycle by removing the fragment from SparceArray
once it it destroyed, in the destroyItem()
method.
Another interesting thing about the adapter above is that, we are using the inbuilt getPageTitle()
method to set the titles for tabs. Usually in most of the implementations we manually set title for tabs by using the method TabLayout.Tab setText()
method, which is not wrong. But using the getPageTitle()
method, makes fragment independent of tabs and also it describes the fragment. Next lets define the layout for MainActivity
class:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> <android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/toolbar" android:background="?attr/colorPrimary" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> </android.support.design.widget.AppBarLayout> <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main" /> </android.support.design.widget.CoordinatorLayout>
Now lets have a look at the onCreate()
method of our activity, where most of the action would happen:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ArrayList<String> tabs = new ArrayList<>(); tabs.add("Tab 1"); tabs.add("Tab 2"); tabLayout = (TabLayout) findViewById(R.id.tab_layout); tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); final ViewPager viewPager = (ViewPager) findViewById(R.id.pager); adapter = new PagerAdapter(getSupportFragmentManager(), tabs); viewPager.setAdapter(adapter); tabLayout.setupWithViewPager(viewPager); }
Since the code base is too large for this example, please find the link to GitHub repo at the end of this page. In the above example to perform Android activity to fragment communication, tabs have been initialized a little differently. As you can see
TabLayout.setupWithViewPager() method is used. This method simplifies the usage of tabs in Android with a ViewPager. It is advised not to use setupWithViewPager()
method in conjunction with setOnTabSelectedListener()
method of TabLayout class. As you would have to do all the implementation manually once again, which is already taken care of, in the setupWithViewPager()
method. Although if you wish to use setOnTabSelectedListener()
please refer to this tutorial, and this stackoverflow link.
Android Tabs: Activity to Fragment Communication
Now that we have a working pager adapter, lets have a look on how to do the android activity to fragment communication with tabs. As suggested in the official docs for Android fragment communication, when an activity needs to deliver a message to the fragment, it can call the fragment’s public methods. Same principle can also be used in case of tabs as well. The only problem one could face; is getting the instance of an already initialized fragment. Although now this problem could be solved by the first section of this tutorial. Continuing from there, to successfully demonstrate android activity to fragment communication, in this tutorial we will call a public method of the fragment through the menu of host activity. To start, lets define a menu item:
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <item android:id="@+id/action_refresh" android:orderInCategory="100" android:title="Refresh" app:showAsAction="always"/> </menu>
This XML would simply create a refresh button on the main activity of our app. For this Android Activity to Fragment Communication tutorial we would make two tabs. Now to deliver a message from an activity to a fragment we need to call its public method from the activity. Therefore first lets define a fragment with a public method:
package com.truiton.activitytofragment; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; public class TabFragment1 extends Fragment implements View.OnClickListener { private IFragmentToActivity mCallback; private Button btnFtoA; private Button btnFtoF; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.tab_fragment_1, container, false); btnFtoA = (Button) view.findViewById(R.id.button); btnFtoF = (Button) view.findViewById(R.id.button2); btnFtoA.setOnClickListener(this); btnFtoF.setOnClickListener(this); return view; } @Override public void onAttach(Context context) { super.onAttach(context); try { mCallback = (IFragmentToActivity) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " must implement IFragmentToActivity"); } } @Override public void onDetach() { mCallback = null; super.onDetach(); } public void onRefresh() { Toast.makeText(getActivity(), "Fragment 1: Refresh called.", Toast.LENGTH_SHORT).show(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.button: mCallback.showToast("Hello from Fragment 1"); break; case R.id.button2: mCallback.communicateToFragment2(); break; } } }
Please have a look at the highlighted lines in the piece of code above. The onRefresh()
method of this fragment above, is a public method. This method would be called from the host activity to perform activity to fragment communication. This method would simply display a Toast message, but will complete the task at hand (please ignore other methods defined in this fragment, as they will be used in next section of this tutorial).
Now lets define two more methods in the MainActivity.java (Full source code available at GitHub, link at the end of this tutorial):
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_refresh) { int position = tabLayout.getSelectedTabPosition(); Fragment fragment = adapter.getFragment(tabLayout .getSelectedTabPosition()); if (fragment != null) { switch (position) { case 0: ((TabFragment1) fragment).onRefresh(); break; case 1: ((TabFragment2) fragment).onRefresh(); break; } } return true; } return super.onOptionsItemSelected(item); }
Above we first created a menu with refresh button, and in the next onOptionsItemSelected()
method of the activity we retrieved the fragment’s instance and called its public method onRefresh()
.
Android Tabs: Fragment to Activity Communication
There are situations when you may need to deliver a message to the host activity of your fragment. This type of communication is called fragment to activity communication. The best way to perform this type of communication is by using an interface and implementing it in the host activity. For this example we would use the below defined interface:
package com.truiton.activitytofragment; public interface IFragmentToActivity { void showToast(String msg); void communicateToFragment2(); }
For this section of the tutorial we would use only the first method, i.e. showToast(String)
. But first lets define the layout for Tab 1 from the previous section:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Tab 1" android:textAppearance="?android:attr/textAppearanceLarge"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView" android:layout_centerHorizontal="true" android:layout_marginTop="30dp" android:text="Fragment to Activity"/> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/button" android:layout_centerHorizontal="true" android:layout_marginTop="30dp" android:text="Fragment to Fragment 2"/> </RelativeLayout>
This would look something like this :
Now to perform Android fragment to activity communication, lets first understand the concept a little. To perform this type of communication, we need to implement the above defined interface in the host activity and define its methods in it. This would allow us to call these newly implemented methods from the fragment itself through the callback. This callback would be nothing but an instance of the host activity itself. In other word we are just calling a public method of an activity. But a callback, tightly couples the fragment to an activity. Therefore it is always advised to use interfaces in case of fragment to activity communication. Also, since we are discussing callbacks, I would like to mention here, please don’t forget to nullify the callback instance in the onDetach() method the fragment. As this could produce a serious memory leak. Next lets implement the interface in our host activity and define its methods:
public class MainActivity extends AppCompatActivity implements IFragmentToActivity { . . . @Override public void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); } @Override public void communicateToFragment2() { TabFragment2 fragment = (TabFragment2) adapter.getFragment(1); if (fragment != null) { fragment.fragmentCommunication(); } else { Log.i(LOG_TAG, "Fragment 2 is not initialized"); } } }
(Please note: full source code is available at GitHub, link at the bottom of page)
Android Tabs: Fragment to Fragment Communication
When implementing tabs in android, one of the most complex problems, is passing data from one fragment to another. But believe me its not that difficult, we can accomplish Android fragment to fragment communication with the help of above pieces of code. In a way, when both Fragment to Activity and Activity to Fragment communication takes place simultaneously it is called Fragment to Fragment communication. Before jumping on to; how this takes place, first lets define the second fragment of our example:
package com.truiton.activitytofragment; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class TabFragment2 extends Fragment implements View.OnClickListener { private IFragmentToActivity mCallback; private TextView mTextView1; private Button btnFtoA; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.tab_fragment_2, container, false); mTextView1 = (TextView) view.findViewById(R.id.textView1); btnFtoA = (Button) view.findViewById(R.id.button); btnFtoA.setOnClickListener(this); return view; } @Override public void onAttach(Context context) { super.onAttach(context); try { mCallback = (IFragmentToActivity) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " must implement IFragmentToActivity"); } } @Override public void onDetach() { mCallback = null; super.onDetach(); } public void onRefresh() { Toast.makeText(getActivity(), "Fragment 2: Refresh called.", Toast.LENGTH_SHORT).show(); } public void fragmentCommunication() { mTextView1.setText("Hello from Tab Fragment 1"); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.button: mCallback.showToast("Hello from Fragment 2"); break; } } }
Its layout:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="20dp" android:textAppearance="?android:attr/textAppearanceMedium"/> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Tab 2" android:textAppearance="?android:attr/textAppearanceLarge"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView" android:layout_centerHorizontal="true" android:layout_marginTop="30dp" android:text="Communicate to Activity"/> </RelativeLayout>
This fragment would look something like this:
To perform Android fragment to fragment communication in tabs lets have a look at the code above. As I indicated earlier, fragment to fragment communication is a combination of fragment to activity and activity to fragment communication. In the above example we have already implemented fragment to fragment communication via the host activity. As you can see above, our host activity implements the IFragmentToActivity
interface. This interface delivers the message from TabFragment1
to the MainActivity
. Further in our MainActivity
we retrieve the TabFragment2
from the PagerAdapter
and call its public method fragmentCommunication()
to deliver the message to that fragment. This process as a whole is called android fragment to fragment communication. For full understanding please have a look at the GitHub repo for this project:
Tabs have become an integral part of the android apps these days. Also as the android framework is evolving, side by side developers are also getting skilled. But at times we need to revisit the basics. This Android activity to fragment tutorial is aimed to reinvent the basics for tabs pattern in Android. If it helped, like, share and follow us on Twitter, Facebook, Google+ and GitHub for more updates.
Born in New Delhi, India. A software engineer by profession, an android enthusiast and an evangelist. My motive here is to create a group of skilled developers, who can develop something new and good. Reason being programming is my passion, and also it feels good to make a device do something you want. In a very short span of time professionally I have worked with many tech firms. As of now too, I am employed as a senior engineer in a leading tech company. In total I may have worked on more than 20 projects professionally, and whenever I get spare time I share my thoughts here at Truiton.
Hi, i need to set img icon inside the tab.
I can change the name but i cant set image.
how do i that?
Hello Mohit Gupt,
thanks for your great tutorial!
I have one question. What is the correct way to communicate betweem 3 Fragments?
Hi Mohit,
I have a problem with my fragments, I have 3 fragments in my activity layout. All the 3 fragments have list view which gets populated with different list of contacts from the database. Whenever I press the the tab to go to the particular tab … the list view in the other 2 fragments gets populated. i.e the list view in fragment which is visible is not populated.Because of this the listviews in the other 2 fragments multiple duplicate data.
The 1st 2 list views are properly populated when the activity opens for the1st time…
can u help !!!
This is a very well written guide, it’s exactly what I was after and has solved my problem.
Thanks Mohit!
Hi Mohit,
Thanks for the tutorial. A very nice one. Please can you help with having multiple (say) two fragments within one tab. I want to be able to replace fragments within the same tab. I will appreciate your guidance.
Nice tutorial, i was looking for this one…
Thank you for the tutorial. I have been struggling for almost two weeks finding a way to communicate two fragments. Thanks to your tutorial I was able to do it…
How to send data from a Fragment to activity? suppose my fragment view contains edit text view and Button. I want to send my edit Text data to activity on Button click .