Pratik Arya
Pratik Arya

Reputation: 11

MvvmCross : Tab Implementation in Android

I am trying to implement tabs through MvvmCross in Xamarin. I came across MvxTabActivity in Android and MvxTabBarViewController in IOS. Both are working well. The problem is MvxTabActivity is obselete. Are there any alternatives for MvxTabActivity?

I found another way to implement this, which uses TabLayout and a ViewPager. The solution asks to use fragments within a fragment. I have pasted the code for this approach. The problem here is on swiping the tabs, all the data in previous tabs is lost. I tried using RetainInstance = true, that gave following exception : "Can't retain fragements that are nested in other fragments."

Product Detail Activity :

[Activity(Label = "ProductDetailView")]
public class ProductDetailView : MvxAppCompatActivity<ProductDetailViewModel>
{
    private FrameLayout _mainFrame;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.product_detail_view);

        if (FindViewById<FrameLayout>(Resource.Id.frame_Detail) != null)
        {
            var frag = new NutritionCategoryView();
            frag.ViewModel = ViewModel.NutritionCategoryModel;
            var trans = SupportFragmentManager.BeginTransaction();
            trans.Replace(Resource.Id.frame_Detail, frag);
            trans.AddToBackStack(null);
            trans.Commit();
        }
    }
}

Nutrition Category View Fragment :

   public class NutritionCategoryView : MvxFragment
{
    public NutritionCategoryViewModel vm
    {
        get { return (NutritionCategoryViewModel) ViewModel; }
    }

    private TabLayout _tablayout;
    private ViewPager _viewPager;


    public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        this.EnsureBindingContextIsSet(inflater);
        var view = this.BindingInflate(Resource.Layout.nutrition_category_view, container, false);

        SetViewPager(view);

        return view;
    }

    private void SetViewPager(View view)
    {
        _viewPager = view.FindViewById<Android.Support.V4.View.ViewPager>(Resource.Id.viewpager);

        if (_viewPager != null)
        {
            var fragments = new List<CategoryTabsAdapter.FragmentInfo>
            {
                new CategoryTabsAdapter.FragmentInfo
                {
                    FragmentType = typeof(CategoryView),
                    Title = "Proximates",
                    ViewModel = vm.Category1
                },
                new CategoryTabsAdapter.FragmentInfo
                {
                    FragmentType = typeof(CategoryView),
                    Title = "Minerals",
                    ViewModel = vm.Category2
                },
                new CategoryTabsAdapter.FragmentInfo
                {
                    FragmentType = typeof(CategoryView),
                    Title = "Fats",
                    ViewModel = vm.Category3
                },
                new CategoryTabsAdapter.FragmentInfo
                {
                    FragmentType = typeof(CategoryView),
                    Title = "Vitamins",
                    ViewModel = vm.Category4
                }
            };

            _viewPager.Adapter = new CategoryTabsAdapter(Activity, ChildFragmentManager, fragments);
        }

        _tablayout = view.FindViewById<TabLayout>(Resource.Id.sliding_tabs);
        _tablayout.SetBackgroundColor(Android.Graphics.Color.Black);
        _tablayout.SetupWithViewPager(_viewPager);
    }
}

Category Tabs Adapter :

public class CategoryTabsAdapter : FragmentStatePagerAdapter
{
    private readonly Context _context;

    public IEnumerable<FragmentInfo> Fragments { get; private set; }

    public CategoryTabsAdapter(Context context, FragmentManager fragmentManager, IEnumerable<FragmentInfo> fragments) : base(fragmentManager)
    {
        _context = context;
        Fragments = fragments;
    }

    public override int Count
    {
        get { return Fragments.Count(); }
    }

    public override Fragment GetItem(int position)
    {
        var fragmentInfo = Fragments.ElementAt(position);

        var fragment = Fragment.Instantiate(_context, Java.Lang.Class.FromType(fragmentInfo.FragmentType).Name);
        ((MvxFragment)fragment).ViewModel = fragmentInfo.ViewModel;
        return fragment;
    }

    public override ICharSequence GetPageTitleFormatted(int position)
    {
        return new Java.Lang.String(Fragments.ElementAt(position).Title);
    }

    public class FragmentInfo
    {
        public string Title { get; set; }

        public Type FragmentType { get; set; }

        public IMvxViewModel ViewModel { get; set; }
    }
}

Category View Fragment

public class CategoryView : MvxFragment<CategoryViewModel>
{
    public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        this.EnsureBindingContextIsSet(inflater);
        var view = this.BindingInflate(Resource.Layout.category_view, container, false);

        //Exception caused here :
        //RetainInstance = true;

        return view;
    }
}

I am new to Xamarin and MvvmCross, so could come up with this much research only. Any solution for either approaches would be of great help.

P.S. This is my first question on Stackoverflow.

Upvotes: 1

Views: 1871

Answers (2)

Drake
Drake

Reputation: 2703

Your tabs are being recreated each time because you are using FragmentStatePagerAdapter instead of FragmentPagerAdapter. From the docs, FragmentPagerAdapter:

This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.

FragmentStatePagerAdapter:

This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

So use FragmentPagerAdapter, but I think it will have to be at the Activity level, and not a fragment within a fragment.

Upvotes: 0

c.lamont.dev
c.lamont.dev

Reputation: 767

I think you need to use the MvxCachingFragmentStatePagerAdapter which is in the MvvmCross.Droid.Support.V4 Nuget package. Then hook it up to your TabLayout with SetupWithViewPager().

        var viewPager = view.FindViewById<ViewPager>(Resource.Id.viewpager);
        if (viewPager != null)
        {
            var fragments = new List<MvxCachingFragmentStatePagerAdapter.FragmentInfo>
            {
                new MvxCachingFragmentStatePagerAdapter.FragmentInfo(
                    "TitleA",
                    typeof (YourFragmentA),
                    typeof (YourViewModelA)),
                new MvxCachingFragmentStatePagerAdapter.FragmentInfo(
                    "TitleB",
                    typeof (YourFragmentB),
                    typeof (YourViewModelB)),
                new MvxCachingFragmentStatePagerAdapter.FragmentInfo(
                    "TitleC",
                    typeof (YourFragmentC),
                    typeof (YourViewModelC))
            };

            viewPager.Adapter = new MvxCachingFragmentStatePagerAdapter(Activity, ChildFragmentManager, fragments);
            viewPager.OffscreenPageLimit = fragments.Count;
            var tabLayout = view.FindViewById<TabLayout>(Resource.Id.tabs);
            tabLayout.SetupWithViewPager(viewPager);
        }

Upvotes: 1

Related Questions