Search code examples
xamarindata-bindingxamarin.androidmvvmcross

How to implement Recycler View in MvvmCross using MvxRecyclerView


In MvvmCross, I have an android listview within an activity that works.

I read somewhere that changing the listView to a recyclerView is as simple as changing MvxListView to MvxRecyclerView in my layout .axml file. Trying that gives me the following runtime exception:

Android.Views.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class Mvx.MvxRecyclerView

Is there anything that I have to do differently in the code behind or view model when using MvxRecyclerView? Below is my code.

Layout files:

Main.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Mvx.MvxRecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        android:id="@+id/words_listview"
        local:MvxItemTemplate="@layout/words_listview_row"
        local:MvxBind="ItemsSource Words" />
</LinearLayout> 

words_listview_row.axml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:p1="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    p1:minWidth="25px"
    p1:minHeight="25px"
    p1:layout_width="match_parent"
    p1:layout_height="match_parent"
    p1:id="@+id/relativeLayout1"
    p1:background="#FFFFFF">
    <TextView
        p1:text="Word name"
        p1:layout_width="wrap_content"
        p1:layout_height="wrap_content"
        p1:id="@+id/headingTextView"
        p1:width="325dp"
        p1:textColor="#000000"
        p1:layout_alignParentLeft="true"
        p1:textSize="18sp"
        p1:paddingLeft="20dp"
        p1:paddingTop="15dp"
        local:MvxBind="Text Name" />
    <TextView
        p1:text="Word meaning"
        p1:layout_width="match_parent"
        p1:layout_height="wrap_content"
        p1:layout_below="@id/headingTextView"
        p1:id="@+id/detailTextView"
        p1:textColor="#8f949a"
        p1:layout_alignParentLeft="true"
        p1:textSize="16sp"
        p1:paddingLeft="20dp"
        p1:paddingRight="20dp"
        p1:paddingBottom="15dp"
        local:MvxBind="Text Meaning" />
</RelativeLayout>

WordViewModel.cs

using MvvmCross.Core.ViewModels;
using VocabBuilder.Core.Models;
using VocabBuilder.Core.Services.Interfaces;

namespace VocabBuilder.Core.ViewModels
{
    public class WordViewModel : MvxViewModel
    {
        private IWordService _wordService;

        public MvxObservableCollection<Word> Words { get; set; }

        public WordViewModel(IWordService wordService)
        {
            Words = new MvxObservableCollection<Word>();

            _wordService = wordService;

            Words.ReplaceWith(_wordService.GetAllWords());
        }
    }
}

Codebehind/ WordView.cs

using Android.App;
using Android.OS;
using MvvmCross.Droid.Views;
using VocabBuilder.Core.ViewModels;

namespace VocabBuilder.UI.Droid.Views
{
    [Activity(Label="Vocab Builder", MainLauncher=true)]
    public class WordView : MvxActivity<WordViewModel>
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
        }
    }
}

Thanks.

EDIT:

Here is my Setup.cs file:

using Android.Content;
using MvvmCross.Core.ViewModels;
using MvvmCross.Droid.Platform;

namespace VocabBuilder.UI.Droid
{
    public class Setup : MvxAndroidSetup
    {
        public Setup(Context applicationContext) : base(applicationContext)
        {
        }

        protected override IMvxApplication CreateApp()
        {
            return new Core.App();
        }
    }
}

Solution

  • OK, I got RecyclerView to work easily after replacing MvxListView with MvxRecyclerView. Not sure what the particular problem with your implementation is but i will give you the tidbits of mine and you can try it all out.

    My RecyclerView lives in a Fragment inheriting from MvxFragment - a bit different than yours but should not be the determining factor. Here are some Fragment snippets:

    public class ListsFragment : MvxFragment<ListsViewModel>
    {
       ...
       public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
       {
          base.OnCreateView(inflater, container, savedInstanceState);
          var view =  this.BindingInflate(Resource.Layout.fragment_listsfragment, null);   
          return view;
       }
       ...
    }
    

    Here is the axml for fragment_listsfragment:

    <?xml version="1.0" encoding="utf-8"?>
    <MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
        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" 
        app:MvxItemTemplate="@layout/item_list"
        app:MvxBind="ItemsSource Lists; ItemClick ShowListItemsCommand" />
    

    I do see a bit of difference in your ViewModel implementation - mine is like this:

    public class ListsViewModel : BaseViewModel
    {
      ...
      private readonly IListService _listService;
      private ObservableCollection<Models.List> _lists;
    
      public ObservableCollection<Models.List> Lists
      {
        get { return _lists; }
        set
        {
          _lists = value;
          RaisePropertyChanged(() => Lists);
        }
      }
    
      public MvxCommand<Models.List> ShowListItemsCommand
      {
        get
        {
          return new MvxCommand<Models.List>(selectedList =>
          {
            ShowViewModel<ListItemsViewModel>
                    (new { listId = selectedList.Id });
          });
        }
      }
      ...
      public override async void Start()
      {
        base.Start();
        await InitializeAsync();
      }
    
      protected override async Task InitializeAsync()
      {
        await _listService.InitializeAsync();
        Lists = (await _listService.GetListsAsync())
                .ToObservableCollection();
      }
    }
    

    Hope this helps.