MvvmCross / MvvmCross-AndroidSupport

Android support library packages for MvvmCross: The .NET MVVM framework for cross-platform solutions.
http://mvvmcross.com
15 stars 0 forks source link

RecyclerView doesn't reflect move operation from an ObservableCollection #310

Open tttyy opened 8 years ago

tttyy commented 8 years ago

Steps to reproduce

  1. Declare an ObservableCollection public ObservableCollection Collection {get;set;} = new ObservableCollection();

    // Initialize somewhere Collection.Add("1"); Collection.Add("2"); Collection.Add("3");

  2. Bind to a RecyclerView using local:MvxBind="ItemsSource Collection"
  3. Call Collection.Move(1, 0);

    Expected behavior

Should visually see the second row move up.

Actual behavior

Nothing visually happens

Configuration

Version: 4.x

kjeremy commented 8 years ago

Is this with 4.3? Can you please upload a reproduction on github.

tttyy commented 8 years ago

I can make one tonight if needed. But I kinda get the idea how it breaks.

The related code is in MvxRecyclerAdapter.cs NotifyDataSetChanged

case NotifyCollectionChangedAction.Move:
    for (int i = 0; i < e.NewItems.Count; i++)
    {
        var oldItem = e.OldItems[i];
        var newItem = e.NewItems[i];
        this.NotifyItemMoved(this.ItemsSource.GetPosition(oldItem), this.ItemsSource.GetPosition(newItem));
        this.RaiseDataSetChanged();
    }
    break;

By moving one entry, both e.NewItems and e.OldItems contains exact same entry. As a result, this.NotifyItemMoved is called with exact same numbers.

I think e.NewStartingIndex and e.OldStartingIndex should be utilized instead.

kjeremy commented 8 years ago

Hm. RaiseDataSetChanged isn't called there... are you looking at the right code?

kjeremy commented 8 years ago

It should probably be something like NotifyItemMoved(e.OldStartingIndex + i, e.NewStartingIndex + i)

kjeremy commented 8 years ago

Maybe our Replace is wrong too... according to https://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedeventargs.olditems(v=vs.110).aspx it should be using OldItems

tttyy commented 8 years ago

BTW I copied code from 4.2. But the main problematic part still keeps. And you get the idea.

Another thing to point out, I'm not sure if the Move operation can have multiple items in OldItems & NewItems. The new & old ranges can overlap which would make things more complicated. But luckily ObeservableCollection seems to only allow moving one item at a time.

kjeremy commented 8 years ago

Ugh those indexes can be -1 in the case of single moves/replaces...

kjeremy commented 8 years ago

Can you create an adapter inheriting from MvxRecyclerAdapter and paste the following code into your adapter

public override void NotifyDataSetChanged(NotifyCollectionChangedEventArgs e)
        {
            try
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        if (e.NewStartingIndex == -1)
                            goto case NotifyCollectionChangedAction.Reset;
                        NotifyItemRangeInserted(e.NewStartingIndex, e.NewItems.Count);
                        break;
                    case NotifyCollectionChangedAction.Move:
                        if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1)
                            goto case NotifyCollectionChangedAction.Reset;
                        for (int i = 0; i < e.NewItems.Count; i++)
                        {
                            var oldPosition = e.OldStartingIndex + i;
                            var newPosition = e.NewStartingIndex + i;
                            NotifyItemMoved(oldPosition, newPosition);
                        }
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        if (e.OldStartingIndex == -1)
                            goto case NotifyCollectionChangedAction.Reset;
                        NotifyItemRangeChanged(e.OldStartingIndex, e.OldItems.Count);
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        if (e.OldStartingIndex == -1)
                            goto case NotifyCollectionChangedAction.Reset;
                        NotifyItemRangeRemoved(e.OldStartingIndex, e.OldItems.Count);
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        NotifyDataSetChanged();
                        break;
                }
            }
            catch (Exception exception)
            {
                Mvx.Warning(
                    "Exception masked during Adapter RealNotifyDataSetChanged {0}. Are you trying to update your collection from a background task? See http://goo.gl/0nW0L6",
                    exception.ToLongString());
            }
        }

Then test your move and some other operations.

tttyy commented 8 years ago

Yea I did similar workaround for move and it worked.

kjeremy commented 8 years ago

Is that the fix though?

On Thu, Oct 27, 2016 at 4:52 PM, tttyy notifications@github.com wrote:

Yea I did similar workaround for move and it worked.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/MvvmCross/MvvmCross-AndroidSupport/issues/310#issuecomment-256765654, or mute the thread https://github.com/notifications/unsubscribe-auth/AEIBRM4wqX_a0qkB3KVSeY5WuUSJf-mRks5q4Q8YgaJpZM4Kio0Q .

tttyy commented 8 years ago

Looks good to me. Thanks!