Excepción con elementos impares en Flowlistview

¡Hola!
El otro día me ocurrió que en algunas páginas que utilizaban listados (con FlowListView) de 2 columnas , se lanzaba la siguiente excepción:

[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] System.NullReferenceException: Object reference not set to an instance of an object.
[MonoDroid]   at Xamarin.Forms.Platform.Android.VisualElementPackager.RemoveChild (Xamarin.Forms.VisualElement view) [0x00007] in D:\agent_work\1\s\Xamarin.Forms.Platform.Android\VisualElementPackager.cs:165 
[MonoDroid]   at Xamarin.Forms.Platform.Android.VisualElementPackager.OnChildRemoved (System.Object sender, Xamarin.Forms.ElementEventArgs e) [0x0000f] in D:\agent_work\1\s\Xamarin.Forms.Platform.Android\VisualElementPackager.cs:152 
[MonoDroid]   at Xamarin.Forms.Element.OnChildRemoved (Xamarin.Forms.Element child) [0x0000f] in D:\agent_work\1\s\Xamarin.Forms.Core\Element.cs:387 
[MonoDroid]   at Xamarin.Forms.VisualElement.OnChildRemoved (Xamarin.Forms.Element child) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\VisualElement.cs:586 
[MonoDroid]   at Xamarin.Forms.Layout`1[T].OnChildRemoved (Xamarin.Forms.Element child) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\Layout.cs:41 
[MonoDroid]   at Xamarin.Forms.Layout.OnInternalRemoved (Xamarin.Forms.View view) [0x00012] in D:\agent_work\1\s\Xamarin.Forms.Core\Layout.cs:427 
[MonoDroid]   at Xamarin.Forms.Layout.InternalChildrenOnCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x0002f] in D:\agent_work\1\s\Xamarin.Forms.Core\Layout.cs:391 
[MonoDroid]   at (wrapper delegate-invoke) .invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
[MonoDroid]   at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in <3e9b3e26c4694baab3f689687ad40612>:0 
[MonoDroid]   at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00009] in <3e9b3e26c4694baab3f689687ad40612>:0 
[MonoDroid]   at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in <3e9b3e26c4694baab3f689687ad40612>:0 
[MonoDroid]   at System.Collections.ObjectModel.Collection`1[T].Remove (T item) [0x00027] in :0 
[MonoDroid]   at Xamarin.Forms.ObservableWrapper`2[TTrack,TRestrict].Clear () [0x00030] in D:\agent_work\1\s\Xamarin.Forms.Core\ObservableWrapper.cs:45 
[MonoDroid]   at DLToolkit.Forms.Controls.FlowListViewInternalCell.UpdateData () [0x0013e] in <8010325400894532bad329e978503ea4>:0 
[MonoDroid]   at DLToolkit.Forms.Controls.FlowListViewInternalCell.OnBindingContextChanged () [0x00006] in <8010325400894532bad329e978503ea4>:0 
[MonoDroid]   at Xamarin.Forms.BindableObject.BindingContextPropertyChanged (Xamarin.Forms.BindableObject bindable, System.Object oldvalue, System.Object newvalue) [0x0000f] in D:\agent_work\1\s\Xamarin.Forms.Core\BindableObject.cs:450 
[MonoDroid]   at Xamarin.Forms.BindableObject.SetValueActual (Xamarin.Forms.BindableProperty property, Xamarin.Forms.BindableObject+BindablePropertyContext context, System.Object value, System.Boolean currentlyApplying, Xamarin.Forms.Internals.SetValueFlags attributes, System.Boolean silent) [0x0011b] in D:\agent_work\1\s\Xamarin.Forms.Core\BindableObject.cs:605 
[MonoDroid]   at Xamarin.Forms.BindableObject.SetValueCore (Xamarin.Forms.BindableProperty property, System.Object value, Xamarin.Forms.Internals.SetValueFlags attributes, Xamarin.Forms.BindableObject+SetValuePrivateFlags privateAttributes) [0x0015b] in D:\agent_work\1\s\Xamarin.Forms.Core\BindableObject.cs:399 
[MonoDroid]   at Xamarin.Forms.BindableObject.SetValue (Xamarin.Forms.BindableProperty property, System.Object value, System.Boolean fromStyle, System.Boolean checkAccess) [0x0005f] in D:\agent_work\1\s\Xamarin.Forms.Core\BindableObject.cs:552 
[MonoDroid]   at Xamarin.Forms.BindableObject.SetValue (Xamarin.Forms.BindableProperty property, System.Object value) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\BindableObject.cs:92 
[MonoDroid]   at Xamarin.Forms.BindableObject.set_BindingContext (System.Object value) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\BindableObject.cs:25 
[MonoDroid]   at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].UpdateContent (TItem content, System.Int32 index, System.Object item) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:614 
[MonoDroid]   at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].UpdateContent (TItem content, System.Int32 index) [0x0000d] in D:\agent_work\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:629 
[MonoDroid]   at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].Xamarin.Forms.ITemplatedItemsList.UpdateContent (TItem content, System.Int32 index) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:633 
[MonoDroid]   at Xamarin.Forms.Platform.Android.ListViewAdapter.GetView (System.Int32 position, Android.Views.View convertView, Android.Views.ViewGroup parent) [0x00155] in D:\agent_work\1\s\Xamarin.Forms.Platform.Android\Renderers\ListViewAdapter.cs:256 
[MonoDroid]   at Android.Widget.BaseAdapter.n_GetView_ILandroid_view_View_Landroid_view_ViewGroup_ (System.IntPtr jnienv, System.IntPtr native__this, System.Int32 position, System.IntPtr native_convertView, System.IntPtr native_parent) [0x0001a] in <1219ce5aae934ab095dc0e05b2110050>:0 
[MonoDroid]   at (wrapper dynamic-method) System.Object.43(intptr,intptr,int,intptr,intptr)

Esta excepción sólo se lanzaba en Android y se daba el caso de que sólo ocurría cuando el listado era impar y tenía más de una columna. Por ejemplo, nos imaginamos que tenemos un listado con 17 elementos, pues al movernos a la última fila se lanza la excepción. En concreto se lanza en el método UpdateData de la clase FlowListViewInternalCell:
El problema ocurre cuando se cambia el BindingContext de la última celda (recordamos que se reciclan las celdas) y justo al eliminar los elementos de la fila ( _rootLayoutAuto.Children.Clear()).
Si echamos un vistazo al XAML del FlowListview tenemos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <flv:FlowListView 
        x:Name="flowListView"
        FlowColumnCount="2" 
        RowHeight="100">
        <flv:FlowListView.FlowColumnTemplate>
            <DataTemplate>
                <StackLayout 
                    CompressedLayout.IsHeadless="true"
                    Margin="5">
                    <Label
                        VerticalOptions="FillAndExpand"
                        BackgroundColor="Olive"
                        Text="{Binding}"/>
                </StackLayout>
            </DataTemplate>
        </flv:FlowListView.FlowColumnTemplate>
    </flv:FlowListView>

El problema que tenemos en nuestro DataTemplate es que estamos añadiendo CompressedLayout en el StackLayout principal, y esto está provocando que al reciclar la celda y eliminar los elementos, se lance la excepción. Realmente no tengo del todo claro el motivo por el que esto sólo ocurre con filas impares, pero si lo eliminamos, no vamos a tener problemas al reciclar la celda:
Este mismo problema lo tenemos también si intentamos limpiar los elementos de un Grid y éstos tienen CompressedLayout.

1
2
3
4
5
6
7
8
9
10
11
12
13
    <ContentPage.Content>
        <Grid 
            x:Name="MainGrid">
            <StackLayout 
                CompressedLayout.IsHeadless="true"
                Padding="5">
                <Label
                    Text="Línea 2"/>
                <Label 
                    Text="Línea 2"/>
            </StackLayout>
        </Grid>
    </ContentPage.Content>

Dejando de lado que la vista no tiene demasiado sentido y tiene elementos innecesarios, si intentamos limpiar el MainGrid (MainGrid.Children.Clear()), va a lanzar excepción:

[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] System.NullReferenceException: Object reference not set to an instance of an object.
[MonoDroid]   at Xamarin.Forms.Platform.Android.VisualElementPackager.RemoveChild (Xamarin.Forms.VisualElement view) [0x00007] in D:\agent_work\1\s\Xamarin.Forms.Platform.Android\VisualElementPackager.cs:165 
[MonoDroid]   at Xamarin.Forms.Platform.Android.VisualElementPackager.OnChildRemoved (System.Object sender, Xamarin.Forms.ElementEventArgs e) [0x0000f] in D:\agent_work\1\s\Xamarin.Forms.Platform.Android\VisualElementPackager.cs:152 
[MonoDroid]   at Xamarin.Forms.Element.OnChildRemoved (Xamarin.Forms.Element child) [0x0000f] in D:\agent_work\1\s\Xamarin.Forms.Core\Element.cs:387 
[MonoDroid]   at Xamarin.Forms.VisualElement.OnChildRemoved (Xamarin.Forms.Element child) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\VisualElement.cs:586 
[MonoDroid]   at Xamarin.Forms.Layout`1[T].OnChildRemoved (Xamarin.Forms.Element child) [0x00000] in D:\agent_work\1\s\Xamarin.Forms.Core\Layout.cs:41 
[MonoDroid]   at Xamarin.Forms.Layout.OnInternalRemoved (Xamarin.Forms.View view) [0x00012] in D:\agent_work\1\s\Xamarin.Forms.Core\Layout.cs:427 
[MonoDroid]   at Xamarin.Forms.Layout.InternalChildrenOnCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x0002f] in D:\agent_work\1\s\Xamarin.Forms.Core\Layout.cs:391 
[MonoDroid]   at (wrapper delegate-invoke) .invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
[MonoDroid]   at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in <3e9b3e26c4694baab3f689687ad40612>:0 
[MonoDroid]   at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00009] in <3e9b3e26c4694baab3f689687ad40612>:0 
[MonoDroid]   at System.Collections.ObjectModel.ObservableCollection`1[T].RemoveItem (System.Int32 index) [0x00021] in <3e9b3e26c4694baab3f689687ad40612>:0 
[MonoDroid]   at System.Collections.ObjectModel.Collection`1[T].Remove (T item) [0x00027] in :0 
[MonoDroid]   at Xamarin.Forms.ObservableWrapper`2[TTrack,TRestrict].Clear () [0x00030] in D:\agent_work\1\s\Xamarin.Forms.Core\ObservableWrapper.cs:45 
[MonoDroid]   at ExceptionFlowListview.MainPage.OnAppearing () [0x00008] in /Users/luismarcos/Documents/Samples/untitled folder/ExceptionFlowListview/ExceptionFlowListview/MainPage.xaml.cs:22 
[MonoDroid]   at Xamarin.Forms.Page.SendAppearing () [0x00024] in D:\agent_work\1\s\Xamarin.Forms.Core\Page.cs:307 
[MonoDroid]   at Xamarin.Forms.Platform.Android.PageRenderer.OnAttachedToWindow () [0x00027] in D:\agent_work\1\s\Xamarin.Forms.Platform.Android\Renderers\PageRenderer.cs:42 
[MonoDroid]   at Android.Views.View.n_OnAttachedToWindow (System.IntPtr jnienv, System.IntPtr native__this) [0x00009] in <1219ce5aae934ab095dc0e05b2110050>:0 
[MonoDroid]   at (wrapper dynamic-method) System.Object.22(intptr,intptr)

Seguramente el problema esté relacionado con que al usar CompressedLayout se elimina la capa y no encuentre la referencia para eliminarlo. En cualquier caso este problema sólo lo encontramos en versiones de Xamarin Forms anteriores a la 3 y puesto que CompressedLayout se introdujo en la 2.5, seguramente no te afecte 🙂

¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *