1

I created a custom widget in Fyne that dynamically adds other widgets.

Everything works fine — until I hide the parent widget and show it again after a few minutes.

When I call .Show() again, all the dynamically added child widgets disappear.
If I show it quickly (before ~2 minutes), it works fine.

It looks like the children are removed or cleaned up after the FYNE_CACHE timeout.

Here’s a simplified example:

func (a *A) CreateRenderer() fyne.WidgetRenderer {
    a.body = container.NewVBox()
    return widget.NewSimpleRenderer(a.body)
}

func (b *B) AddItem(item fyne.CanvasObject) {
    b.body.Add(item)
}

Why do the dynamically added items disappear after hiding/unhiding the widget? How can I make them persist across cache rebuilds?

1
  • 1
    Print a log in “CreateRenderer” and you’ll see it called a second time. Then your code will reset the body to an empty container. Commented Oct 8 at 22:04

1 Answer 1

1

The issue happens because Fyne discards the renderer of hidden widgets after a cache timeout, and then recreates it using your CreateRenderer() method.

Any objects added after CreateRenderer() are lost unless you store them persistently.

You can fix this by:

  • Keeping a persistent slice of your child objects (items []fyne.CanvasObject)

  • Rebuilding your container from that slice in CreateRenderer()

  • Adding new widgets to both the container and the slice when you create them dynamically

Here’s the full example that works:

package main

import (
    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/widget"
)

// Base widget A
type A struct {
    widget.BaseWidget
    body  *fyne.Container
    items []fyne.CanvasObject // persist dynamically added items
}

func NewA() *A {
    a := &A{}
    a.ExtendBaseWidget(a)
    return a
}

func (a *A) CreateRenderer() fyne.WidgetRenderer {
    a.body = container.NewVBox()
    for _, item := range a.items {
        a.body.Add(item)
    }
    return widget.NewSimpleRenderer(a.body)
}

// Derived widget B that adds dynamic items
type B struct {
    A
}

func NewB() *B {
    b := &B{}
    b.ExtendBaseWidget(b)
    return b
}

func (b *B) AddItem(item fyne.CanvasObject) {
    b.items = append(b.items, item) // persist
    b.body.Add(item)
    b.body.Refresh()
}

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Dynamic Fyne Widget")

    b := NewB()
    addBtn := widget.NewButton("Add Label", func() {
        b.AddItem(widget.NewLabel("Dynamic Item"))
    })

    w.SetContent(container.NewVBox(addBtn, b))
    w.ShowAndRun()
}
  1. Fyne may destroy and recreate renderers for hidden widgets after a cache timeout (to save memory).

  2. If you added new child widgets after CreateRenderer() was first called, they are not known to the new renderer.

  3. By keeping your items slice persistent in the widget itself, you can rebuild your UI inside CreateRenderer() when Fyne recreates it.

  4. Using widget.NewSimpleRenderer(a.body) is fine here, but for more advanced cases, you can implement a custom renderer struct.

Sign up to request clarification or add additional context in comments.

1 Comment

This is such a good answer. I’ll just add the key thing here that a renderer should just show graphical representation - the state and important details should all be stored in the Widget.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.