Realm-dotnet: Android Xamarin.Forms setting ListView.SelectedItem = null causes app to hang/crash when using IQueryable<T> as ItemsSource in v1.6.0

Created on 23 Sep 2017  路  21Comments  路  Source: realm/realm-dotnet

I'm attaching a solution which reproduces an issue I'm having since updating to 1.6.0 of Realm.

Basically, on Android, when we have a Xamarin.Forms ListView and use IQueryable<TRealmObject> as the ItemsSource and inside the ListView.ItemSelected event handler, set the ListView.SelectedItem = null; it's causing the app to crash / hang without a usable managed stack trace.

This works fine in v1.5.0 of Realm. It does not seem to affect iOS.

RealmFormsSelectedItemRepro.zip

O-Community

All 21 comments

Side note, this targets android 8.0 so you'll need the Xamarin alpha channel installed to use the repro solution. I'm running this on an Android Oreo device as well, not sure that matters though.

I took only a brief look at the repro code (haven't been able to run it yet) and I'd say it's very surprising since that shouldn't affect Realm at all unless the data-binding engine is doing something it's not supposed to do. Unfortunately, we're quite busy this week so won't be able to look into it until early next week. In the meantime, if you investigate further, keep us posted and we'll do our best to offer assistance.

I was able to identify the problem - it's caused by Realm collections conforming to the IList interface added by #1469. When I remove the IList conformance, things work as expected and you don't get the crash. Considering, it's working fine on iOS and I can see no reason why setting a view property to null is affected by its datasource's interface conformance, I am inclined to think it's a bug with Xamarin.Forms on Android or Xamarin.Android itself.

Hey, i run into the same problem with IRealmCollection. Did you find a solution?

Unfortunately, we don't have anything yet.

Also having the same problem and it's killing my project.

Have the same issue. Do you have any plans of fixing it?

Is there any suggestion for a hack or workaround while we wait for an official fix?

i subscribe to the change event and add each item to an observablecollection/remove items that are deleted.

@davidmclennan As a workaround you can downgrade to v1.5.0 since the bug is introduced in v1.6.0

@yuv4ik When I uninstall the latest stable, then install 1.5 and run I get:

System.EntryPointNotFoundException: shared_realm_install_callbacks

When my code hits Realm.GetInstance(). Does this mean the old version is still somehow installed?

@davidmclennan If I remember correctly I had to completely uninstall the previous version of the app with Realm 1.6.0 on the targeting device before deploying the new app with Realm 1.5.0, due to some exception. Unfortunately cannot recall it now.

@yuv4ik Uninstalling the app alone still gave me the error but a clean + rebuild fixed it, for anyone else having the problem. Thank you!

Have spent tones of time trying to understand what's the cause.... I'm having the same issue.

I hit the same problem, on 3.0.0 and 2.0.0. Any idea if there is an official fix incoming?

There will be a fix, but we don't have timeline yet.

Is there a workaround for 3.0.0? I am running into the same issue.

@ctearpak I am using a simple workaround that uses the Subscribe for notifications method built into Realm Queries.

    public partial class SimpleList : ContentPage
    {

        ObservableCollection<Item> observableItems = new ObservableCollection<Item>();
        IRealmCollection<Item> rawItems;

        public class SimpleList()
        {
            InitializeComponent();

            rawItems = realm.All<Item>().AsRealmCollection();

            foreach (Item item in rawItems)
            {
                observableItems.Add(item);
            }

            rawItems.SubscribeForNotifications((sender, changes, error) =>
            {
                if (changes != null)
                {
                    foreach (int i in changes.DeletedIndices)
                    {
                        observableItems.RemoveAt(i);
                    }

                    foreach (int i in changes.InsertedIndices)
                    {
                        observableItems.Insert(i, rawItems[i]);
                    }
                    foreach (int i in changes.ModifiedIndices)
                    {
                        observableItems.RemoveAt(i);
                        observableItems.Insert(i, rawItems[i]);
                    }

                }

                if (error != null)
                {
                    // TODO: Handle Exceptions
                }


            });

            MyListView.ItemsSource = observableItems;

        }

        async void Handle_ItemSelected(object sender, ItemTappedEventArgs e)
        {

            if (e.Item == null)
            {
                return;
            }

            // DO Stuff

            // Deselect Item
            ((ListView)sender).SelectedItem = null;
        }

    }

Create an observable list that will be bound to the front end ListView (ObservableItems in this example), and populate it with the elements return from the initial query. Then use the SubscribeForNotifications method to listen to any changes to the live query. In this example I am just updating the Observable List to match exactly the live query. According to the documentation, deleted indices refer to the state the list was before the notified changes (i.e. should be done first), and changed and added indices refer to the state of the list after the notified changes (i.e. should be done after removing the indices).
This should give you the desired functionality until a fix is pushed. Hope this helps!

EDIT: I forgot to add, this worked on me on 2.0, but it should work on 3.x

@CruzatAdrian: Thank you. This did work to solve as a workaround...

However, I just switched my application over to using Prism, and don't seem to have this issue with the following (pseudo) code...

```C#
public class ListViewDemoViewModel: ViewModelBase
{
ObservableCollection _allObjects;
MyObject _selectedObject;

public ObservableCollection<MyObject> AllObjects 
{ 
   get 
   { 
        return _allObjects; 
    } 
    private set 
    {   
        SetProperty(ref _allIObjects, value); 
     } 
 }

public MyObject SelectedObject 
{ 
   get 
   { 
        return _selectedObject; 
    } 
    private set 
    {   
        SetProperty(ref _selectedObject, value); 
     } 
 }

 public ICommand ObjectTapped => new Command<MyObject> ( async (i) => await SelectObject(i));

 async Task SelectObject(MyObject o)
 {
      ...
      SelectedObject = null;
 }

 ...

}

The following ListView XAML ties it all together...

```XML
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
             x:Class="MyApp.Views.ListViewDemoPage"
             >
    <ContentPage.Content>
        <ListView
            ItemsSource="{Binding AllObjects}"
            VerticalOptions="Fill" BackgroundColor="Transparent" 
            CachingStrategy="RecycleElement"
            SelectedItem="{Binding SelectedObject, Mode=TwoWay}"
            >
            <ListView.Behaviors>
                <b:EventToCommandBehavior EventName="ItemTapped"
                                          Command="{Binding ObjectTapped}" 
                                          EventArgsParameterPath="Item"/>

            </ListView.Behaviors>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextCell Text="{Binding SomeProperty}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ContentPage.Content>
</ContentPage>

Maybe this will help someone who is thinking of switching to using Prism, or is in an early dev stage. It looks like the ViewModelBase's BindableBase takes care of this stuff. updates to the object using prism are updating in the table, so at least I know that the change events are firing.

If you use Prism, are you familiar with how this also works?

It's all new to me, so if something that I put in here doesn't make sense, or is an anti-pattern, let me know.... But it seems to have solved my problem (and Prism is pretty cool.)

EDIT:
You still need to apply the workaround for things to disappear from the list when the list is updated...
I wrote a class to do this wherever it is needed:

```C#
public static class RealmCollectionWorkaround
{
public static void NotificationSubscription(IRealmCollection realmCollection, ObservableCollection observable)
{
realmCollection.SubscribeForNotifications((IRealmCollection sender, ChangeSet changes, Exception error) =>
{
if (changes != null)
{
foreach (int i in changes.DeletedIndices)
observable.RemoveAt(i);
foreach (int i in changes.InsertedIndices)
observable.Insert(i, realmCollection[i]);
foreach (int i in changes.ModifiedIndices)
{
observable.RemoveAt(i);
observable.Insert(i, realmCollection[i]);
}
}

            if (error != null)
            {
                Console.WriteLine("Exception thrown\n " + error);
            }
        });
    }
}

```

Thanks for the fix.
Works for me with 3.1.0-alpha-392 from myGet 馃憤

Was this page helpful?
0 / 5 - 0 ratings