dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
21.96k stars 1.71k forks source link

.NET MAUI Problem with rendering RecyclerView.ViewHolder on Android #21895

Closed QuestionerTN closed 2 months ago

QuestionerTN commented 4 months ago

Description

I have the following exception:

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object.
[mono-rt]    at Microsoft.Maui.Controls.Compatibility.Platform.Android.CellFactory.GetCell(Cell item, View convertView, ViewGroup parent, Context context, View view)
[mono-rt]    at EditableListViewRenderer.ListViewHolder.BindCell(Int32 position, EditableListView listview, Activity context, Dictionary`2 cache) in EditableListViewRenderer.cs:line 99
[mono-rt]    at EditableListViewRenderer.EditableListAdapter.OnBindViewHolder(ViewHolder holder, Int32 position) in EditableListViewRenderer.cs:line 141
[mono-rt]    at AndroidX.RecyclerView.Widget.RecyclerView.Adapter.n_OnBindViewHolder_Landroidx_recyclerview_widget_RecyclerView_ViewHolder_I(IntPtr jnienv, IntPtr native__this, IntPtr native_holder, Int32 position) in androidx.recyclerview.recyclerview\obj\Release

Steps to Reproduce

Call ListViewHolder.BindCell() in a class derived from EditableListView. The sample program is below:

using Android.App;
using Android.Content;
using Android.Views;
using Android.Widget;
using System.Collections;
using AView = Android.Views.View;

using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
using AndroidX.RecyclerView.Widget;

namespace App.Droid
{
    [Obsolete]
    //#########################################################################
    public class EditableListViewRenderer : ViewRenderer<EditableListView, RecyclerView>
    {
        Context context;
        EditableListAdapter adapter;
        ItemTouchHelper helper;

        public EditableListViewRenderer(Context context) : base(context)
        {
            this.context = context;
        }

        protected override void Dispose(bool disposing)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<EditableListView> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    RecyclerView control = new RecyclerView(context);

                    if (Element.CanReorder)
                    {
                        helper = new ItemTouchHelper(new ItemTouchHelperCallback(e.NewElement, ItemTouchHelper.Up | ItemTouchHelper.Down, 0));
                        helper.AttachToRecyclerView(control);
                    }

                    adapter = new EditableListAdapter(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity, Element, control, helper);
                    control.SetAdapter(adapter);

                    var layoutManager = new LinearLayoutManager(context);
                    control.SetLayoutManager(layoutManager);

                    SetNativeControl(control);
                }
            }
        }

        //#####################################################################
        public class ItemTouchHelperCallback : ItemTouchHelper.SimpleCallback
        {
            public ItemTouchHelperCallback(EditableListView view, int dragDirs, int swipeDirs) : base(dragDirs, swipeDirs)
            {
            }

            public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
            {
                return true;
            }

            public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
            {
            }

            public override void OnSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
            {
            }
        }

        //#####################################################################
        public class ListViewHolder : RecyclerView.ViewHolder, IOnTouchListener
        {
            public Cell Cell { get; private set; }
            public ViewGroup parent { get; set; }

            public ListViewHolder(AView itemView, ViewGroup parent, EditableListAdapter adapter) : base(itemView)
            {
                this.parent = parent;
            }

            public void BindCell (int position, EditableListView listview, Activity context, Dictionary<Cell, AView> cache)
            {
                Cell = (Cell) listview.TemplatedItems[position];
                AView cell;

                if (cache.ContainsKey (Cell)) {
                    cell = cache[Cell];
                } else {
                    cell = CellFactory.GetCell (Cell, ItemView, parent, context, listview); // @@@ Exception!!
                    cache.Add (Cell, cell);
                }
            }

            public bool OnTouch(AView view, MotionEvent me)
            {
                return false;
            }
        }

        //#####################################################################
        public class EditableListAdapter : RecyclerView.Adapter
        {
            Activity context;
            EditableListView listview;

            ITemplatedItemsView<Cell> TemplatedItemsView => listview;
            IList Items => listview.ItemsSource as IList;
            Dictionary<Cell, AView> _cache;

            public EditableListAdapter(Activity context, EditableListView listview, RecyclerView recyclerview, ItemTouchHelper helper) : base()
            {
                this.context = context;
                this.listview = listview;
                _cache = new Dictionary<Cell, AView> ();
            }

            public override long GetItemId(int position)
            {
                return position;
            }

            public override int ItemCount => Items != null ? Items.Count : 0;

            public override void OnViewRecycled(Java.Lang.Object holder)
            {
            }

            public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
            {
                ListViewHolder vh = (ListViewHolder)holder;
                vh.BindCell (position, listview, context, _cache);
            }

            public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
            {
                AView view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.EditableListItemView, parent, false);
                AView reorder = view.FindViewById<ImageView>(Resource.Id.Reorder);
                reorder.Visibility = listview.CanReorder ? ViewStates.Visible : ViewStates.Gone;
                AView remove = view.FindViewById<ImageView>(Resource.Id.Remove);
                remove.Visibility = listview.CanDelete ? ViewStates.Visible : ViewStates.Gone;
                ListViewHolder vh = new ListViewHolder(view, parent, this);
                return vh;
            }
        }
    }
}

Link to public reproduction project repository

none

Version with bug

8.0.3 GA

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Not version dependent

Did you find any workaround?

Unknown/Other

Relevant log output

No response
QuestionerTN commented 2 months ago

I was able to resolve this issue myself. Thank you for your cooperation.

public void BindCell (int position, EditableListView listview, Activity context, Dictionary<Cell, AView> cache)
{
    Cell = (Cell) listview.TemplatedItems[position];
    AView cell;

    if (cache.ContainsKey (Cell)) {
        cell = cache[Cell];
    } else {
        //----- Update Start
        // cell = CellFactory.GetCell (Cell, ItemView, parent, context, listview); // @@@ Exception!!
        cell = Microsoft.Maui.Controls.Handlers.Compatibility.CellFactory.GetCell (Cell, ItemView, parent, context, listview);
        //----- Update End
        cache.Add (Cell, cell);
    }
}
QuestionerTN commented 2 months ago

Closed.