Skip to content

Commit

Permalink
Merge pull request #1066 from fabulous-dev/disposable-component
Browse files Browse the repository at this point in the history
Dispose properly ViewNode and Component when Widget is removed from tree
  • Loading branch information
TimLariviere authored Jan 30, 2024
2 parents 1933f98 + 2931784 commit 0d5f4c2
Show file tree
Hide file tree
Showing 14 changed files with 495 additions and 209 deletions.
56 changes: 36 additions & 20 deletions src/Fabulous.Tests/APISketchTests/TestUI.Attributes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@ module TestUI_Attributes =

let btn = node.Target :?> IButton

match node.TryGetHandler<int>(name) with
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handlerId -> btn.RemovePressListener handlerId
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)
| ValueNone -> node.RemoveHandler(name)

| ValueSome msg ->
let handler () = Dispatcher.dispatch node msg

let handlerId = btn.AddPressListener handler
node.SetHandler<int>(name, ValueSome handlerId))

let disposable =
{ new IDisposable with
member _.Dispose() = btn.RemovePressListener handlerId }

node.SetHandler(name, disposable))
)
|> AttributeDefinitionStore.registerScalar

Expand All @@ -44,18 +48,22 @@ module TestUI_Attributes =

let btn = node.Target :?> IButton

match node.TryGetHandler<int>(name) with
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handlerId -> btn.RemoveTapListener handlerId
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)
| ValueNone -> node.RemoveHandler(name)

| ValueSome msg ->
let handler () = Dispatcher.dispatch node msg

let handlerId = btn.AddTapListener handler
node.SetHandler<int>(name, ValueSome handlerId))

let disposable =
{ new IDisposable with
member _.Dispose() = btn.RemoveTapListener handlerId }

node.SetHandler(name, disposable))
)
|> AttributeDefinitionStore.registerScalar

Expand All @@ -70,18 +78,22 @@ module TestUI_Attributes =

let btn = node.Target :?> IButton

match node.TryGetHandler<int>(name) with
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handlerId -> btn.RemoveTap2Listener handlerId
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)
| ValueNone -> node.RemoveHandler(name)

| ValueSome msg ->
let handler () = Dispatcher.dispatch node msg

let handlerId = btn.AddTap2Listener handler
node.SetHandler<int>(name, ValueSome handlerId))

let disposable =
{ new IDisposable with
member _.Dispose() = btn.RemoveTap2Listener handlerId }

node.SetHandler(name, disposable))
)
|> AttributeDefinitionStore.registerScalar

Expand All @@ -96,18 +108,22 @@ module TestUI_Attributes =

let btn = node.Target :?> IContainer

match node.TryGetHandler<int>(name) with
match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handlerId -> btn.RemoveTapListener handlerId
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)
| ValueNone -> node.RemoveHandler(name)

| ValueSome msg ->
let handler () = Dispatcher.dispatch node msg

let handlerId = btn.AddTapListener handler
node.SetHandler<int>(name, ValueSome handlerId))

let disposable =
{ new IDisposable with
member _.Dispose() = btn.RemoveTapListener handlerId }

node.SetHandler(name, disposable))
)
|> AttributeDefinitionStore.registerScalar

Expand Down
7 changes: 6 additions & 1 deletion src/Fabulous.Tests/APISketchTests/TestUI.Component.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ module TestUI_Component =
let ComponentProperty = "ComponentProperty"

let getComponent (target: obj) =
(target :?> TestViewElement).PropertyBag.Item ComponentProperty
match (target :?> TestViewElement).PropertyBag.TryGetValue(ComponentProperty) with
| true, comp -> comp
| _ -> null

let setComponent (comp: obj) (target: obj) =
(target :?> TestViewElement).PropertyBag.Add(ComponentProperty, comp)
3 changes: 2 additions & 1 deletion src/Fabulous.Tests/APISketchTests/TestUI.Widgets.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module TestUI_Widgets =
| ValueNone -> None
| ValueSome parent -> Some parent

let viewNode = ViewNode(parentNode, context, weakReference)
let viewNode = new ViewNode(parentNode, context, weakReference)

view.PropertyBag.Add(ViewNode.ViewNodeProperty, viewNode)

Expand Down Expand Up @@ -214,6 +214,7 @@ module TestUI_Widgets =
MinLogLevel = LogLevel.Fatal }
Dispatch = fun msg -> unbox<'msg> msg |> x.ProcessMessage
GetComponent = Component.getComponent
SetComponent = Component.setComponent
SyncAction = fun fn -> fn() }

member x.ProcessMessage(msg: 'msg) =
Expand Down
45 changes: 17 additions & 28 deletions src/Fabulous/Attributes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ module Attributes =

// Trigger the unmounted event
Dispatcher.dispatchEventForAllChildren itemNode widget Lifecycle.Unmounted
itemNode.Disconnect()
itemNode.Dispose()

// Remove the child from the UI tree
targetColl.RemoveAt(index)
Expand Down Expand Up @@ -247,7 +247,7 @@ module Attributes =

// Trigger the unmounted event for the old child
Dispatcher.dispatchEventForAllChildren prevItemNode oldWidget Lifecycle.Unmounted
prevItemNode.Disconnect()
prevItemNode.Dispose()

// Replace the existing child in the UI tree at the index with the new one
targetColl[index] <- unbox view
Expand Down Expand Up @@ -288,20 +288,16 @@ module Attributes =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: MsgValue voption) node ->
let event = getEvent node.Target

match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> event.RemoveHandler handler
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)

| ValueNone -> node.RemoveHandler(name)
| ValueSome(MsgValue msg) ->
let handler = EventHandler(fun _ _ -> Dispatcher.dispatch node msg)

event.AddHandler handler
node.SetHandler(name, ValueSome handler))
let event = getEvent node.Target
let handler = event.Subscribe(fun _ -> Dispatcher.dispatch node msg)
node.SetHandler(name, handler))
)

|> AttributeDefinitionStore.registerScalar
Expand All @@ -317,23 +313,21 @@ module Attributes =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: ('args -> MsgValue) voption) (node: IViewNode) ->
let event = getEvent node.Target

match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> event.RemoveHandler handler
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)

| ValueNone -> node.RemoveHandler(name)
| ValueSome fn ->
let event = getEvent node.Target

let handler =
EventHandler<'args>(fun _ args ->
event.Subscribe(fun args ->
let (MsgValue r) = fn args
Dispatcher.dispatch node r)

node.SetHandler(name, ValueSome handler)
event.AddHandler handler)
node.SetHandler(name, handler))
)
|> AttributeDefinitionStore.registerScalar

Expand All @@ -347,20 +341,15 @@ module Attributes =
SimpleScalarAttributeDefinition.CreateAttributeData(
ScalarAttributeComparers.noCompare,
(fun _ (newValueOpt: (unit -> unit) voption) node ->
let event = getEvent(node.Target)

match node.TryGetHandler(name) with
| ValueNone -> ()
| ValueSome handler -> event.RemoveHandler handler
| ValueSome handler -> handler.Dispose()

match newValueOpt with
| ValueNone -> node.SetHandler(name, ValueNone)

| ValueNone -> node.RemoveHandler(name)
| ValueSome(fn) ->
let handler = EventHandler(fun _ _ -> fn())

event.AddHandler handler
node.SetHandler(name, ValueSome handler))
let event = getEvent node.Target
node.SetHandler(name, event.Subscribe(fun _ -> fn())))
)

|> AttributeDefinitionStore.registerScalar
Expand Down
Loading

0 comments on commit 0d5f4c2

Please sign in to comment.