Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ListView: when adding/removing items, app immediately crashes on Windows, raise 'ItemTemplate count has exceeded the limit of 23' Exception on Android #41

Open
heldap opened this issue Jul 19, 2023 · 2 comments

Comments

@heldap
Copy link

heldap commented Jul 19, 2023

Code:

namespace DebugView

open Fabulous
open Fabulous.Maui

open type Fabulous.Maui.View

module App =
    let mutable id = 0
    type Item =
        {
            Name: string
            Detail: string
        }
        static member create () = 
            let i = {Name= $"Dummy{id}"; Detail="Dummy Dum"}
            id <- id + 1
            i

    type Model = { Items: Item list }

    type Msg = 
    | Add 
    | RemoveLast 

    let init () = { Items = [Item.create()] }, Cmd.none

    let removeLast = 
        function
        | [] -> []
        | l -> List.removeAt (l.Length-1) l

    let update msg model =
        match msg with
        | Add -> {model with Items = List.append model.Items [Item.create()]}, Cmd.none
        | RemoveLast -> {model with Items = removeLast model.Items}, Cmd.none

    let view model =
        Application(
            ContentPage(
                (VStack(){
                    ListView model.Items (fun i -> TextCell(i.Name).detailText(i.Detail))
                    HStack(){
                        Button("Add", Add)
                        Button("Remove Last", RemoveLast)
                    }
                })
            )
        )

    let program = Program.statefulWithCmd init update view

On Windows Machine target, whether I click Add or Remove, the app crash: it seems that any attempt to modify ListView's items results in a crash (Win32 exception)

Here is the log:

Debug: Unhandled exception: System.ArgumentException: Value is an invalid value for ItemTemplate (Parameter 'value')
   at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value, Boolean fromStyle, Boolean checkAccess)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value)
   at [email protected](FSharpValueOption`1 _arg1, FSharpValueOption`1 newValueOpt, IViewNode node)
   at Fabulous.ScalarAttributeDefinitions.CreateAttributeData@73-1.Invoke(FSharpValueOption`1 oldValueOpt, FSharpValueOption`1 newValueOpt, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](a _arg1, WidgetCollectionItemChanges diffs, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](WidgetDiff diff, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](WidgetDiff diff, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at Fabulous.Reconciler.update(FSharpFunc`2 canReuseView, FSharpValueOption`1 prevOpt, Widget next, IViewNode node)
   at [email protected](Unit unitVar0)
Debug: Unhandled exception: System.ArgumentException: Value is an invalid value for ItemTemplate (Parameter 'value')
   at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value, Boolean fromStyle, Boolean checkAccess)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value)
   at [email protected](FSharpValueOption`1 _arg1, FSharpValueOption`1 newValueOpt, IViewNode node)
   at Fabulous.ScalarAttributeDefinitions.CreateAttributeData@73-1.Invoke(FSharpValueOption`1 oldValueOpt, FSharpValueOption`1 newValueOpt, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](a _arg1, WidgetCollectionItemChanges diffs, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](WidgetDiff diff, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](WidgetDiff diff, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at Fabulous.Reconciler.update(FSharpFunc`2 canReuseView, FSharpValueOption`1 prevOpt, Widget next, IViewNode node)
   at [email protected](Unit unitVar0)
   at [email protected]()
   at Microsoft.Maui.ApplicationModel.MainThread.BeginInvokeOnMainThread(Action action)
   at [email protected](FSharpFunc`2 arg00)
   at Fabulous.ViewAdapters.ViewAdapter`3.OnStateChanged(StateChangedEventArgs args)
'DebugView.exe' (CoreCLR: clrhost): Loaded 'D:\Projects\_Apps\DebugView\DebugView\bin\Debug\net7.0-windows10.0.19041.0\win10-x64\AppX\System.IO.Compression.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Debug: Unhandled exception: System.ArgumentException: Value is an invalid value for ItemTemplate (Parameter 'value')
   at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value, Boolean fromStyle, Boolean checkAccess)
   at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value)
   at [email protected](FSharpValueOption`1 _arg1, FSharpValueOption`1 newValueOpt, IViewNode node)
   at Fabulous.ScalarAttributeDefinitions.CreateAttributeData@73-1.Invoke(FSharpValueOption`1 oldValueOpt, FSharpValueOption`1 newValueOpt, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](a _arg1, WidgetCollectionItemChanges diffs, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](WidgetDiff diff, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at [email protected](WidgetDiff diff, IViewNode node)
   at Fabulous.ViewNode.Fabulous.IViewNode.ApplyDiff(WidgetDiff& diff)
   at Fabulous.Reconciler.update(FSharpFunc`2 canReuseView, FSharpValueOption`1 prevOpt, Widget next, IViewNode node)
   at [email protected](Unit unitVar0)
   at [email protected]()
   at Microsoft.Maui.ApplicationModel.MainThread.BeginInvokeOnMainThread(Action action)
   at [email protected](FSharpFunc`2 arg00)
   at Fabulous.ViewAdapters.ViewAdapter`3.OnStateChanged(StateChangedEventArgs args)
   at [email protected](StateChangedEventArgs arg00)
   at Microsoft.FSharp.Control.CommonExtensions.SubscribeToObservable@2281.System.IObserver<'T>.OnNext(T args) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 2283
   at Microsoft.FSharp.Control.FSharpEvent`1.Trigger(T arg) in D:\a\_work\1\s\src\FSharp.Core\event.fs:line 175
   at Fabulous.Runners.Runner`4.dispatch(msg msg)
The program '[11072] DebugView.exe' has exited with code 3221226107 (0xc000027b).

On Android, with both emulator (Android 10.0, API 29) and my phone (Android 6.0 API 23);
after a while adding and removing items, I get a System.Exception:'ItemTemplate count has exceeded the limit of 23 Please make sure to reuse DataTemplate objects

@heldap heldap changed the title ListView crashes when adding/removing items, immediately crashes on Windows, provoke ItemTemplate count has exceeded the limit of 23 on Android ListView: when adding/removing items, app immediately crashes on Windows, raise 'ItemTemplate count has exceeded the limit of 23' Exception on Android Jul 19, 2023
@TimLariviere
Copy link
Member

Thanks for the report!
I managed to reproduce with the example given 👍

What's happening in your case is that Fabulous expects your items to be a stable reference value (aka mutable list) instead of a non-mutable one (like F# list type where a new list instance is created with each mutation).

If the reference changes, Fabulous will consider the whole list needs to be reloaded and will lose the DataTemplates that were created previously -- causing the crash on Android after reaching the limit of .NET MAUI.

A workaround would be to change the Model type:

type Model = { Items: ObservableCollection<Item> }

Note: I'm using ObservableCollection instead of any System.Collections.IList type, because .NET MAUI expects the collection to raise events when adding/removing items which ObservableCollection do

Then, you can still add and remove items like usual.

let init () =
    let items = ObservableCollection<Item>()
    items.Add(Item.create())
    { Items = items }, Cmd.none

let update msg model =
    match msg with
    | Add ->
        model.Items.Add(Item.create())
        model, Cmd.none
    | RemoveLast ->
        if model.Items.Count > 0 then
            model.Items.RemoveAt(model.Items.Count - 1)
        
        model, Cmd.none

This approach will work, though it has some drawbacks because of the mutated collection (especially if you want clean separation between states).

Virtualized collection widgets like ListView are very tricky to work with in Fabulous, and can easily crash.
I'm not really satisfied with our implementation.

@heldap
Copy link
Author

heldap commented Jul 21, 2023

Thanks you for the quick reply :)
This workaround is fine for my needs.

May I close this issue ?
It could be interesting to keep it opened though, as someone else might encounter this issue (since using F# list is instinctive): keeping it opened makes the workaround visible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants