15

I am developing an app that has a tab layout as the image.

enter image description here

I’d like to use MVVM architecture with data binding library but I am new with this framework.

I can do this without using MVVM by normally setup tab layout using ViewPager as this sample.

Normal tab layout without MVVM and data binding:

activity_main.xml:

    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"
            app:tabGravity="fill"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"  />
</android.support.design.widget.CoordinatorLayout>

MainActivity.java:

    public class MainActivity extends AppCompatActivity {

    private Toolbar toolbar;
    private TabLayout tabLayout;
    private ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        viewPager = (ViewPager) findViewById(R.id.viewpager);
        setupViewPager(viewPager);

        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
    }

    private void setupViewPager(ViewPager viewPager) {
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(new OneFragment(), "ONE");
        adapter.addFragment(new TwoFragment(), "TWO");
        adapter.addFragment(new ThreeFragment(), "THREE");
        viewPager.setAdapter(adapter);
    }

    class ViewPagerAdapter extends FragmentPagerAdapter {
        private final List<Fragment> mFragmentList = new ArrayList<>();
        private final List<String> mFragmentTitleList = new ArrayList<>();

        public ViewPagerAdapter(FragmentManager manager) {
            super(manager);
        }

        @Override
        public Fragment getItem(int position) {
            return mFragmentList.get(position);
        }

        @Override
        public int getCount() {
            return mFragmentList.size();
        }

        public void addFragment(Fragment fragment, String title) {
            mFragmentList.add(fragment);
            mFragmentTitleList.add(title);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mFragmentTitleList.get(position);
        }
    }
}

Tab layout in MVVM:

When using MVVM with data binding library, we will have to use a view model for the tab layout view.

And I don’t know how to setup tab layout in the XML and also in the view model. How to handle events such as “tap on one tab of the layout” using data binding library

Is there any sample of using Tab layout in MVVM with data binding library?

Thanks for your help.

hamada147
  • 495
  • 1
  • 8
  • 14
Jimy25
  • 247
  • 1
  • 3
  • 11

4 Answers4

22

MainActivity -

    public class MainActivity extends Activity 
    {
        @Override
        protected void onCreate(@Nullable final Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            App.get(this).component().inject(this);
            ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            binding.setHandler(this);
            binding.setManager(getSupportFragmentManager());
        }
        
        @BindingAdapter({"bind:handler"})
        public static void bindViewPagerAdapter(final ViewPager view, final MainActivity activity)
        {
            final MainActionsAdapter adapter = new MainActionsAdapter(view.getContext(), activity.getSupportFragmentManager());
            view.setAdapter(adapter);
        }
        
        @BindingAdapter({"bind:pager"})
        public static void bindViewPagerTabs(final TabLayout view, final ViewPager pagerView)
        {
            view.setupWithViewPager(pagerView, true);
        }
    
    }

xml -

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:fresco="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
    
            <import type="android.view.View" />
    
            <variable
                name="handler"
                type="com.ui.main.MainActivity" />
    
            <variable
                name="manager"
                type="android.support.v4.app.FragmentManager" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:animateLayoutChanges="true"
                app:title="@string/app_name"
                app:titleMarginStart="8dp" />
    
            <android.support.design.widget.TabLayout
                android:id="@+id/tab_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:pager="@{(pager)}">
            </android.support.design.widget.TabLayout>
    
            <android.support.v4.view.ViewPager
                android:id="@+id/pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:handler="@{handler}" />
    
        </LinearLayout>
    
    </layout>

Adapter -

    public class MainSectionsAdapter extends FragmentPagerAdapter
    {
        private static final int CONTACTS = 0;
        private static final int CALLS = 1;
        private static final int CHATS = 2;
        
        private static final int[] TABS = new int[]{CONTACTS, CALLS, CHATS};
        
        private Context mContext;
        
        public MainSectionsAdapter(final Context context, final FragmentManager fm)
        {
            super(fm);
            mContext = context.getApplicationContext();
        }
        
        @Override
        public Fragment getItem(int position)
        {
            switch (TABS[position])
            {
                case CONTACTS:
                    return ContactsFragment.newInstance();
                case CALLS:
                    return CallsFragment.newInstance();
                case CHATS:
                    return ChatsFragment.newInstance();
            }
            return null;
        }
        
        @Override
        public int getCount()
        {
            return TABS.length;
        }
        
        @Override
        public CharSequence getPageTitle(int position)
        {
            switch (TABS[position])
            {
                case CONTACTS:
                    return mContext.getResources().getString(R.string.contacts);
                case CALLS:
                    return mContext.getResources().getString(R.string.calls);
                case CHATS:
                    return mContext.getResources().getString(R.string.chats);
            }
            return null;
        }
    }
Dmitrijs
  • 1,333
  • 1
  • 14
  • 20
  • Can you show the code behind MainActionsAdapter? Thanks – Riley MacDonald Aug 10 '17 at 06:47
  • added to answer – Dmitrijs Aug 14 '17 at 15:43
  • I'm trying to add tab layout in a fragment, which means I would need to use getChildFragmentManager. Do you know how could I accomplish this? – Anton Makov Jan 28 '18 at 22:01
  • @AntonMakov have you found a way when using Fragments? – Bruno Bieri May 10 '18 at 14:45
  • We didn't found a solution on how to use childfragmentmanager wile binding data and eventually we used moved everything to the parent fragment and as the result we didn't need to use childfragmentmanager any more. – Anton Makov May 13 '18 at 12:26
  • Cannot find the setter for attribute 'app:pager' with parameter type androidx.viewpager.widget.ViewPager on com.google.android.material.tabs.TabLayout – Mitesh Vanaliya Jul 04 '19 at 08:45
  • @MiteshVanaliya check your BindingAdapter, if you copied mine, it adds bind:pager to support VIewPager, not "androidx" – Dmitrijs Jul 04 '19 at 09:11
  • @Dmitrijs I have used this code but no tabs are appearing on the screen only blue bar at the bottom, I have also replaced the fragment names from MainSectionsAdapter with my app fragments but it is still not working. – suv Jul 16 '19 at 10:19
  • @suv This code snippet is from working application, I can't debug your code from a distance, sorry – Dmitrijs Jul 16 '19 at 12:53
  • @Dmitrijs Its working now, i forgot to apply to change the contentView in MainActivity. Do you know how can i put icons along with texts? I have gone through a lot of SO threads but none seems to be working like https://stackoverflow.com/questions/31260384/how-to-add-page-title-and-icon-in-android-fragmentpageradapter/31261273#31261273 – suv Jul 16 '19 at 13:26
  • nice answer, what if I need to set the text to textView when tabs clicked how can I achieve this @Dmitrijs – EL TEGANI MOHAMED HAMAD GABIR Aug 24 '19 at 10:19
  • Good approach ,but it does not work with Viewpager2 – joghm Mar 05 '20 at 11:05
  • I have already created the BindingAdapter as well and being able to import for app:pager but I am not being able to import app:handler tag inside viewpager. What might be the issue? – bhaskar Jul 30 '20 at 08:48
  • I wouldnt use Activity or Fragment instances data binding. It causes memory leak. – Emre Aktürk Jan 21 '21 at 17:52
4

I'm not sure if this is newly introduced recently but with Android Support version 27.1.1, you don't even even need a custom data binding adapter, you can simply use:

<android.support.design.widget.TabLayout
                android:id="@+id/tl_1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:setupWithViewPager="@{some_fragment_viewpager}"
                app:tabSelectedTextColor="@android:color/white"
                app:tabTextColor="@color/v5_grey_55"
                />


        <android.support.v4.view.ViewPager
            android:id="@+id/some_fragment_viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:addOnPageChangeListener="@{vm.pageChangeListener}"
            app:setAdapter="@{vm.pageAdapter}"
            app:setCurrentItem="@{vm.pageChangeListener.currentPosition}"
            />

Take note that the viewPager variable in app:setupWithViewPager="@{some_fragment_viewpager}" points to android:id="@+id/some_fragment_viewpager". That's how the reference to the ViewPager is done (like magic I know)!

ViewModel

public class SomeViewModel {
  public ViewPager.OnPageChangeListener pageChangeListener;
  public SomeFragmentPagerAdapter pagerAdapter;
  // ...
}

FragmentPagerAdapter

public classs SomeFragmentPagerAdapter extends FragmentPagerAdapter {
  public Boolean currentPosition;
}
ericn
  • 12,476
  • 16
  • 84
  • 127
3

Here is my solution for setUpWithViewpager using databinding:

    public class BindingUtil
    {
        @BindingAdapter({ "setUpWithViewpager" })
        public static void setUpWithViewpager(final TabLayout tabLayout, ViewPager viewPager)
        {
            viewPager.addOnAdapterChangeListener(new ViewPager.OnAdapterChangeListener()
            {
                @Override
                public void onAdapterChanged(@NonNull ViewPager viewPager, @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter)
                {
                    if (oldAdapter == null && (newAdapter == null || newAdapter.getCount() == 0))
                    {
                        // this function will helpful when 
                        // we don't create viewpager immediately 
                        // when view created (this mean we create
                        // will pager after a period time)
                        return;
                    }
                    tabLayout.setupWithViewPager(viewPager);
                }
            });
        }
    }

xml

<android.support.design.widget.TabLayout
     ... 
     app:setUpWithViewpager="@{ viewPager }"
/>

<android.support.v4.view.ViewPager
     ...
     android:id="@+id/viewPager"
     app:adapter="@{viewModel.pagerAdapter}"
/>

ViewModel

    public class MainViewModel extends BaseObservable
    {
        
        @Bindable
        public PagerAdapter getPagerAdapter()
        {
            return adapter;
        }
    
        private void createViewPagerAdapter()
        {
            ...
            notifyPropertyChanged(BR.pagerAdapter);
        }
    }

full demo project here

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Linh
  • 57,942
  • 23
  • 262
  • 279
0

as Shown in Phan Van Linh's answer, It's working for me but it shows binding error while we provide viewPager's ID as reference to tabLayout in brackets.

I solved this by simply providing the id without brackets and also provide ViewPager's ID in camelCase.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61