Nick King
Nick King

Reputation: 200

Mvvmcross Viewpager with list items

I am trying to implement viewpager like effect on my flattened list. So, when the fragment with the viewpager is open the initial screen shows "Item1" from the list and when the user slides, the screen changes to "Item2". Now I have implemented an example using viewPagerAdapter but I am inflating the view inside the adapter which is not the mvvm way. I want to be able to update the view from the viewmodel.

Here is the code for what I have implemented so far:

This is my PagerAdapter

public class CustomPagerAdapter : PagerAdapter
{
    private readonly Context _context;
    private readonly List<ListItem> _stringLst;
    private readonly ViewPagerExampleViewModel _viewModel;
    private readonly ViewPager _viewPager;

    public CustomPagerAdapter(Context context, List<ListItem> stringLst, ViewPagerExampleViewModel viewModel, ViewPager viewPager)
    {
        _context = context;
        _stringLst = stringLst;
        _viewModel = viewModel;
        _viewPager = viewPager;
    }

    public override bool IsViewFromObject(View view, Object @object)
    {
        return view == @object;
    }

    public override int Count
    {
        get
        {
            var count = _stringLst.Count;
            return count;
        }
    }

    public override Object InstantiateItem(ViewGroup container, int position)
    {
        if (_context.GetSystemService(Context.LayoutInflaterService) is LayoutInflater inflater)
        {
            var view = inflater.Inflate(Resource.Layout.slidertextview, null);
            var child = view.FindViewById<TextView>(Resource.Id.sliderText);
            var nextButton = view.FindViewById<Button>(Resource.Id.nestedNextButton);
            var sectionButton = view.FindViewById<Button>(Resource.Id.nestedSectionButton);

            nextButton.Click += ButtonOnClick;
            sectionButton.Click += SectionButtonOnClick;

            var title = _stringLst[position].Title;
            child.Text = title;

            container.AddView(view);

            return view;
        }

        return null;
    }

    private void SectionButtonOnClick(object sender, EventArgs e)
    {
        _viewModel.SectionJumpCommand.Execute();

        _viewPager.CurrentItem = _viewModel.Index;
    }

    private void ButtonOnClick(object sender, EventArgs e)
    {
        _viewModel.NextCommand.Execute();

        _viewPager.CurrentItem = _viewModel.Index;
    }

    public override void DestroyItem(ViewGroup container, int position, Object @object)
    {
        container.RemoveView((View)@object);
    }

    public override void SetPrimaryItem(ViewGroup container, int position, Object @object)
    {
        base.SetPrimaryItem(container, position, @object);

        _viewModel.Index = position;
    }
}

My ViewModel looks like this:

private List<ListItem> _items;
    public List<ListItem> Items
    {
        get => _items;
        set
        {
            if (_items == value)
                return;

            _items = value;
            RaisePropertyChanged(nameof(Items));
        }
    }

    private int _index;
    public int Index
    {
        get => _index;
        set
        {
            if (_index == value)
                return;

            _index = value;
            RaisePropertyChanged(nameof(Index));
        }
    }

    public IMvxCommand NextCommand => new MvxCommand(Next);
    public IMvxCommand SectionJumpCommand => new MvxCommand(SectionJump);

    public ViewPagerExampleViewModel()
    {
        Index = 0;

        Items = new List<ListItem> {
            new ListItem { Title = "Item 0" },
            new ListItem { Title = "Item 1" },
            new ListItem { Title = "Item 2" },
            new ListItem { Title = "Item 3" },
            new ListItem { Title = "Item 4" },
            new ListItem { Title = "Item 5" },
            new ListItem { Title = "Item 6" },
            new ListItem { Title = "Item 7" },
            new ListItem { Title = "Item 8" },
            new ListItem { Title = "Item 9" },
            new ListItem { Title = "Item 10" },
            new ListItem { Title = "Item 11" },
            new ListItem { Title = "Item 12" }
        };
    }

    public void Next()
    {
        if (Items.Count == Index + 1)
            return;

        Index += 1;
    }

    private void SectionJump()
    {
        if (Items.Count == Index + 1)
            return;

        Index += 3;
    }

And my fragment:

public class ViewPagerExampleFragment : BaseFragment<ViewPagerExampleViewModel>
{
    private ViewPager _viewPager;
    private FloatingActionButton _nextButton;

    protected override int FragmentId => Resource.Layout.viewpagerExampleView;

    public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        var view = base.OnCreateView(inflater, container, savedInstanceState);

        _nextButton = view.FindViewById<FloatingActionButton>(Resource.Id.nextButton);

        _viewPager = view.FindViewById<ViewPager>(Resource.Id.viewpager);
        _viewPager.Adapter = new CustomPagerAdapter(Context, ViewModel.Items, ViewModel, _viewPager);

        return view;
    }
}

Can someone help me on how to make this process more Mvvm. I don't want to update my item layout from the adapter instead it has to be updated from my viewmodel.

Upvotes: 1

Views: 518

Answers (1)

Saamer
Saamer

Reputation: 5109

As you can see in the PeopleView of the StarWars sample (in the official MVVMCross samples repo), the magic happens in the layout file itself.

So your fragment is clean and just needs to declare that it’s using that layout. In your layout, just assign the ItemSource to the property in your ViewModel and you’re good. You just need the MvxRecyclerView section shown below

<MvxSwipeRefreshLayout
        android:id="@+id/refresher"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        local:layout_behavior="@string/appbar_scrolling_view_behavior"
        local:MvxBind="IsRefreshing LoadPeopleTask; RefreshCommand RefreshPeopleCommand">
        <MvxRecyclerView
            android:id="@+id/people_recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            local:MvxItemTemplate="@layout/item_name"
            local:MvxBind="ItemsSource People; ItemClick PersonSelectedCommand" />
    </MvxSwipeRefreshLayout>

Let me know if that makes sense. You could also use the MvxListView if you like

Upvotes: 1

Related Questions