core
Package core provides the widget and element framework interfaces and lifecycle.
This package defines the foundational types for building reactive user interfaces: Widget, Element, State, and BuildContext. It follows a declarative UI model where widgets describe what the UI should look like, and the framework efficiently updates the actual UI to match.
Core Types
Widget is an immutable description of part of the UI. Widgets are lightweight configuration objects that can be created frequently without performance concerns.
Element is the instantiation of a Widget at a particular location in the tree. Elements manage the lifecycle and identity of widgets.
Stateful Widgets
For widgets that need mutable state, embed StateBase in your state struct:
type myState struct {
core.StateBase
count int
}
func (s *myState) InitState() {
// Initialize state here
}
func (s *myState) Build(ctx core.BuildContext) core.Widget {
return widgets.Text{Content: fmt.Sprintf("Count: %d", s.count)}
}
State Management
Signal provides thread-safe reactive values with automatic rebuild:
s.counter = core.NewSignal(0)
core.UseListenable(s, s.counter) // Subscribe for rebuilds
s.counter.Set(5) // Triggers rebuild
Hooks
UseDisposable and UseListenable help manage resources and subscriptions with automatic cleanup on disposal.
Constructor Conventions
Controllers and services use NewX() constructors returning pointers:
ctrl := animation.NewAnimationController(time.Second)
channel := platform.NewMethodChannel("app.channel")
This distinguishes long-lived, mutable objects (controllers) from immutable configuration objects (widgets, which use struct literals or XxxOf() helpers).
Package core provides the core widget and element framework.
Variables
DebugMode controls whether debug information is displayed in error widgets. When true, error widgets show detailed error messages and stack traces. When false, error widgets show minimal information.
var DebugMode = true
func GlobalOffsetOf
func GlobalOffsetOf(element Element) graphics.Offset
GlobalOffsetOf returns the accumulated offset for an element in the render tree.
func MustProvide
func MustProvide[T any](ctx BuildContext) T
MustProvide finds and depends on the nearest ancestor InheritedProvider[T]. Panics if not found in the ancestor chain.
Example:
user := core.MustProvide[*User](ctx)
fmt.Println("Hello,", user.Name)
func Provide
func Provide[T any](ctx BuildContext) (T, bool)
Provide finds and depends on the nearest ancestor InheritedProvider[T]. Returns the value and true if found, or the zero value and false if not found.
Example:
if user, ok := core.Provide[*User](ctx); ok {
fmt.Println("Hello,", user.Name)
}
func SetDebugMode
func SetDebugMode(debug bool)
SetDebugMode enables or disables debug mode for the framework.
func SetErrorWidgetBuilder
func SetErrorWidgetBuilder(builder ErrorWidgetBuilder)
SetErrorWidgetBuilder configures the global error widget builder. Pass nil to restore the default builder.
func UseDisposable
func UseDisposable(s stateBase, d Disposable)
UseDisposable registers a Disposable resource for automatic cleanup when the state is disposed. Call this once in InitState, not in Build.
Example:
func (s *myState) InitState() {
s.animation = animation.NewAnimationController(300 * time.Millisecond)
core.UseDisposable(s, s.animation)
}
func UseListenable
func UseListenable(s stateBase, listenable Listenable)
UseListenable subscribes to a Listenable and triggers rebuilds. The subscription is automatically cleaned up when the state is disposed.
Works with Signal, Derived, Notifier, and any custom type that implements Listenable.
The listener callback calls StateBase.SetState, which is not thread-safe. If the source is mutated from background goroutines, wrap the mutation with drift.Dispatch to ensure notifications arrive on the UI thread.
Example:
func (s *myState) InitState() {
s.counter = core.NewSignal(0)
core.UseListenable(s, s.counter)
}
func (s *myState) Build(ctx core.BuildContext) core.Widget {
return widgets.Text{Content: fmt.Sprintf("Count: %d", s.counter.Value())}
}
func UseSelector
func UseSelector[S comparable](st stateBase, source Listenable, selector func() S)
UseSelector subscribes to a Listenable source but only triggers rebuilds when the selector returns a different value. The selector extracts the relevant portion of state; the widget only rebuilds when that portion changes.
S must be comparable. For non-comparable selected types (slices, maps), use UseSelectorWithEquality instead.
This is useful when a signal holds a large struct but the widget only depends on one field. Without a selector, every notification would trigger a rebuild.
Call this once in InitState, not in Build. The subscription is automatically cleaned up when the state is disposed.
Like other hooks, the listener callback calls SetState, which is not thread-safe. If the source is mutated from background goroutines, wrap the Set call with drift.Dispatch to ensure notifications arrive on the UI thread.
Example:
func (s *myState) InitState() {
// Only rebuilds when user.Name changes, ignoring other field updates
core.UseSelector(s, s.user, func() string {
return s.user.Value().Name
})
}
func UseSelectorWithEquality
func UseSelectorWithEquality[S any](st stateBase, source Listenable, selector func() S, equal func(a, b S) bool)
UseSelectorWithEquality is like UseSelector but accepts a custom equality function for comparing selected values. Use this when the selected type is non-comparable (slices, maps) or when you need semantic equality that differs from Go's == operator:
core.UseSelectorWithEquality(s, s.store, func() []string {
return s.store.Value().Tags
}, slices.Equal)
type AspectAwareInheritedWidget
AspectAwareInheritedWidget is implemented by inherited widgets that support granular per-dependent rebuild filtering based on registered aspects. Most inherited widgets do not need this; the default behavior notifies all dependents when [InheritedWidget.ShouldRebuildDependents] returns true.
When implemented, [ShouldRebuildDependent] is called for each dependent that registered specific aspects via [BuildContext.DependOnInherited]. Dependents that registered with a nil aspect (depending on all changes) are always notified.
type AspectAwareInheritedWidget interface {
InheritedWidget
// ShouldRebuildDependent returns true if a specific dependent should
// rebuild based on the aspects it registered. The aspects map contains all
// aspects the dependent registered via [BuildContext.DependOnInherited].
ShouldRebuildDependent(oldWidget InheritedWidget, aspects map[any]struct{}) bool
}
type BuildContext
BuildContext provides access to the widget tree during the build phase.
BuildContext is passed to [StatelessWidget.Build] and [State.Build] methods, providing access to inherited data and ancestor widgets.
The most common use is accessing InheritedWidget data:
func (w MyWidget) Build(ctx core.BuildContext) core.Widget {
theme := theme.ThemeOf(ctx) // Uses DependOnInherited internally
return Text{Content: "Hello", Style: theme.TextTheme.BodyLarge}
}
type BuildContext interface {
// Widget returns the widget that created this context.
Widget() Widget
// FindAncestor walks up the tree and returns the first element matching
// the predicate, or nil if none is found.
FindAncestor(predicate func(Element) bool) Element
// DependOnInherited finds the nearest ancestor [InheritedWidget] of the given
// type and registers this context as a dependent. When the inherited widget
// updates and notifies dependents, this widget will rebuild.
//
// The aspect parameter enables granular dependency tracking: when non-nil,
// only changes affecting that aspect trigger rebuilds. Pass nil to rebuild
// on any change. Use [reflect.TypeOf] to get the inherited widget's type:
//
// themeType := reflect.TypeOf(MyTheme{})
// widget := ctx.DependOnInherited(themeType, "colors")
//
// For simpler access, use [InheritedProvider] with [Provide].
DependOnInherited(inheritedType reflect.Type, aspect any) any
// DependOnInheritedWithAspects is like DependOnInherited but registers multiple
// aspects in a single tree walk. More efficient when depending on several aspects
// of the same inherited widget.
DependOnInheritedWithAspects(inheritedType reflect.Type, aspects ...any) any
}
type BuildOwner
BuildOwner tracks dirty elements that need rebuilding.
type BuildOwner struct {
// OnNeedsFrame is called when a new element is scheduled for rebuild,
// signalling the platform that a frame should be rendered. This is
// necessary for on-demand frame scheduling where the display link is
// paused until explicitly requested.
OnNeedsFrame func()
// contains filtered or unexported fields
}
func NewBuildOwner
func NewBuildOwner() *BuildOwner
NewBuildOwner creates a new BuildOwner.
func (*BuildOwner) FlushBuild
func (b *BuildOwner) FlushBuild()
FlushBuild rebuilds all dirty elements in depth order.
func (*BuildOwner) NeedsWork
func (b *BuildOwner) NeedsWork() bool
NeedsWork returns true if there are dirty elements or pending layout/paint.
func (*BuildOwner) Pipeline
func (b *BuildOwner) Pipeline() *layout.PipelineOwner
Pipeline returns the PipelineOwner for render object scheduling.
func (*BuildOwner) RegisterGlobalKey
func (b *BuildOwner) RegisterGlobalKey(key any, element Element)
RegisterGlobalKey associates a global key identity with an element in the owner's registry. This is called automatically by the framework when an element whose widget returns a GlobalKey is mounted. If a key is already registered, the new element silently replaces the previous entry.
The key parameter is the inner identity pointer obtained from globalKeyRegistry.globalKeyImpl(), not the GlobalKey value itself.
func (*BuildOwner) ScheduleBuild
func (b *BuildOwner) ScheduleBuild(element Element)
ScheduleBuild marks an element as needing rebuild.
func (*BuildOwner) UnregisterGlobalKey
func (b *BuildOwner) UnregisterGlobalKey(key any, element Element)
UnregisterGlobalKey removes a global key registration, but only if the currently registered element matches the provided element. This guard prevents a stale unmount from removing a registration that has already been claimed by a newly mounted element with the same key.
type Derived
Derived is a read-only reactive value that recomputes from source signals automatically. When any dependency fires its Listenable callback, the compute function is re-evaluated. If the new value differs from the previous one (checked via equality), all listeners are notified.
Derived is safe for concurrent use. Reads use a shared lock and writes (recomputation) use an exclusive lock, following the same RWMutex pattern as Signal.
Derived satisfies Listenable via Derived.AddListener. Derived values can be chained: one Derived can serve as a dependency of another.
Equality
By default, the old and new values are compared with interface comparison (any(old) != any(new)). This works for all comparable types (int, string, structs with comparable fields, etc.). For non-comparable types such as slices or maps, provide a custom equality function via NewDerivedWithEquality to avoid a runtime panic.
Lifecycle
A Derived subscribes to its dependencies on creation. Call Derived.Dispose to unsubscribe from all dependencies and release resources. Inside a stateful widget, use UseDerived to create, subscribe, and auto-dispose a Derived in one step.
Example
firstName := core.NewSignal("John")
lastName := core.NewSignal("Doe")
fullName := core.NewDerived(func() string {
return firstName.Value() + " " + lastName.Value()
}, firstName, lastName)
fmt.Println(fullName.Value()) // "John Doe"
lastName.Set("Smith")
fmt.Println(fullName.Value()) // "John Smith"
type Derived[T any] struct {
// contains filtered or unexported fields
}
func NewDerived
func NewDerived[T comparable](compute func() T, deps ...Listenable) *Derived[T]
NewDerived creates a Derived that recomputes when any dep changes. The compute function is called immediately to establish the initial value, then called again each time a dependency notifies.
Values are compared with == to skip redundant notifications. For non-comparable types (slices, maps), use NewDerivedWithEquality instead; the comparable constraint here ensures a compile-time error rather than a runtime panic.
func NewDerivedWithEquality
func NewDerivedWithEquality[T any](compute func() T, equal func(a, b T) bool, deps ...Listenable) *Derived[T]
NewDerivedWithEquality creates a Derived with a custom equality function for comparing old and new computed values. If equal is nil, the default interface comparison is used.
Use this when T is non-comparable (slices, maps) or when you need semantic equality that differs from Go's == operator:
items := core.NewDerivedWithEquality(
func() []string { return buildList(source.Value()) },
slices.Equal,
source,
)
func UseDerived
func UseDerived[T comparable](s stateBase, compute func() T, deps ...Listenable) *Derived[T]
UseDerived creates a Derived, subscribes to it for rebuilds, and auto-disposes it when the state is disposed. This is a convenience that combines NewDerived + UseListenable + OnDispose(d.Dispose) in one call.
Call this once in InitState, not in Build. Like UseListenable, the listener calls StateBase.SetState, so dependencies must only be mutated on the UI thread (use drift.Dispatch from goroutines).
Example:
type myState struct {
core.StateBase
firstName *core.Signal[string]
lastName *core.Signal[string]
fullName *core.Derived[string]
}
func (s *myState) InitState() {
s.firstName = core.NewSignal("John")
s.lastName = core.NewSignal("Doe")
s.fullName = core.UseDerived(s, func() string {
return s.firstName.Value() + " " + s.lastName.Value()
}, s.firstName, s.lastName)
}
func (s *myState) Build(ctx core.BuildContext) core.Widget {
return widgets.Text{Content: s.fullName.Value()}
}
func UseDerivedWithEquality
func UseDerivedWithEquality[T any](s stateBase, compute func() T, equal func(a, b T) bool, deps ...Listenable) *Derived[T]
UseDerivedWithEquality is like UseDerived but accepts a custom equality function for comparing computed values. Use this when the derived type is non-comparable (slices, maps):
func (s *myState) InitState() {
s.tags = core.UseDerivedWithEquality(s, func() []string {
return buildTagList(s.source.Value())
}, slices.Equal, s.source)
}
func (*Derived[T]) AddListener
func (d *Derived[T]) AddListener(fn func()) func()
AddListener adds a callback that fires when the derived value changes. Use Derived.Value to read the new value inside the callback. This method satisfies the Listenable interface, enabling chained derivations where one Derived is a dependency of another. Returns an unsubscribe function.
func (*Derived[T]) Dispose
func (d *Derived[T]) Dispose()
Dispose unsubscribes from all dependencies and clears listeners. After disposal, the Derived will no longer recompute and any outstanding listener references become no-ops. Dispose is safe to call multiple times.
func (*Derived[T]) ListenerCount
func (d *Derived[T]) ListenerCount() int
ListenerCount returns the number of registered listeners.
func (*Derived[T]) Value
func (d *Derived[T]) Value() T
Value returns the current derived value. Safe for concurrent use.
type Disposable
Disposable is an interface for types that need cleanup.
type Disposable interface {
Dispose()
}
type Element
Element is the instantiation of a Widget at a specific location in the tree.
While widgets are immutable configuration objects, elements are the mutable counterparts that manage the widget's lifecycle in the tree. Each widget creates an element when mounted, and the element persists across widget rebuilds as long as the widget type and key match.
Elements form a tree parallel to the widget tree. When a parent widget rebuilds, the framework compares new widgets against existing elements to determine whether to update, replace, or remove elements.
Most developers don't interact with elements directly - the framework manages them automatically. Custom elements are only needed for advanced use cases like building new layout primitives.
type Element interface {
// Widget returns the current widget configuration for this element.
Widget() Widget
// Mount attaches this element to the tree under the given parent.
Mount(parent Element, slot any)
// Update reconfigures this element with a new widget of the same type.
Update(newWidget Widget)
// Unmount removes this element from the tree permanently.
Unmount()
// MarkNeedsBuild schedules this element for rebuild in the next frame.
MarkNeedsBuild()
// RebuildIfNeeded performs the rebuild if this element is marked dirty.
RebuildIfNeeded()
// VisitChildren calls the visitor for each child element.
VisitChildren(visitor func(Element) bool)
// Depth returns this element's depth in the tree (root is 0).
Depth() int
// Slot returns the slot identifier for this element in its parent.
Slot() any
// UpdateSlot changes the slot identifier for this element.
UpdateSlot(newSlot any)
}
func MountRoot
func MountRoot(root Widget, owner *BuildOwner) Element
MountRoot inflates and mounts the root widget with the provided build owner.
type ErrorBoundaryCapture
ErrorBoundaryCapture is implemented by error boundary elements to capture errors from descendant widgets.
type ErrorBoundaryCapture interface {
// CaptureError captures an error from a descendant widget.
// Returns true if the error was captured and handled.
CaptureError(err *errors.BoundaryError) bool
}
type ErrorWidgetBuilder
ErrorWidgetBuilder creates a fallback widget when a widget build fails. The builder receives the boundary error and should return a widget to display in place of the failed widget.
type ErrorWidgetBuilder func(err *errors.BoundaryError) Widget
func GetErrorWidgetBuilder
func GetErrorWidgetBuilder() ErrorWidgetBuilder
GetErrorWidgetBuilder returns the current error widget builder.
type GlobalKey
GlobalKey provides cross-tree access to an element's State and BuildContext. The type parameter S is the concrete State type returned by GlobalKey.CurrentState.
A GlobalKey is a value type that wraps a pointer; identity comes from the pointer, so two GlobalKey values wrapping the same pointer compare as equal via reflect.DeepEqual (the mechanism used by the framework's widget reconciliation). Two calls to NewGlobalKey always produce distinct keys.
When a widget returns a GlobalKey from its Key() method, the framework registers the element in the BuildOwner's global key registry on mount and removes it on unmount. This enables looking up the element, its state, or its context from anywhere in the application, regardless of tree position.
GlobalKey works with all element types (stateless, stateful, render object, inherited). However, GlobalKey.CurrentState only returns a non-zero value for stateful elements whose State satisfies the type parameter S.
Thread Safety
GlobalKey's accessor methods (CurrentState, CurrentElement, CurrentContext) read from fields that are written during mount/unmount on the UI thread. Call these methods from the UI thread only.
Example
var formKey = core.NewGlobalKey[*formState]()
type formWidget struct {
core.StatefulBase
}
func (w formWidget) Key() any { return formKey }
func (formWidget) CreateState() core.State { return &formState{} }
type formState struct {
core.StateBase
// ...
}
func (s *formState) Validate() bool { /* ... */ return true }
// From a sibling or parent widget, without passing references:
if state := formKey.CurrentState(); state != nil {
state.Validate()
}
type GlobalKey[S State] struct {
// contains filtered or unexported fields
}
func NewGlobalKey
func NewGlobalKey[S State]() GlobalKey[S]
NewGlobalKey creates a new GlobalKey with a unique identity. Each call returns a key that is distinct from every other key, so widget reconciliation will never match two widgets with different GlobalKey instances.
Store the key in a package-level variable or a long-lived struct field so that the same key is used across rebuilds:
var myKey = core.NewGlobalKey[*myState]()
func (GlobalKey[S]) CurrentContext
func (k GlobalKey[S]) CurrentContext() BuildContext
CurrentContext returns the BuildContext for this key's element, or nil. The returned context is valid only while the element is mounted.
func (GlobalKey[S]) CurrentElement
func (k GlobalKey[S]) CurrentElement() Element
CurrentElement returns the Element currently mounted with this key, or nil.
func (GlobalKey[S]) CurrentState
func (k GlobalKey[S]) CurrentState() S
CurrentState returns the State associated with this key's element, or the zero value of S if no element is currently mounted with this key. For non-stateful elements (stateless, render object, inherited) this always returns the zero value.
type IndexedSlot
IndexedSlot represents a child's position in a multi-child parent.
type IndexedSlot struct {
Index int
PreviousSibling Element
}
type InheritedBase
InheritedBase provides default CreateElement and Key implementations for inherited widgets. Embed it in your widget struct along with a Child field and implement [InheritedWidget.ShouldRebuildDependents] and [InheritedWidget.ChildWidget]:
type UserScope struct {
core.InheritedBase
User *User
Child core.Widget
}
func (u UserScope) ChildWidget() core.Widget { return u.Child }
func (u UserScope) ShouldRebuildDependents(old core.InheritedWidget) bool {
return u.User != old.(UserScope).User
}
type InheritedBase struct{}
func (InheritedBase) CreateElement
func (InheritedBase) CreateElement() Element
CreateElement returns a new InheritedElement.
func (InheritedBase) Key
func (InheritedBase) Key() any
Key returns nil (no key).
type InheritedElement
InheritedElement is the element that hosts an InheritedWidget and manages the dependency tracking for descendant widgets.
When a descendant calls [BuildContext.DependOnInherited], it registers as a dependent of this element. When the InheritedWidget is updated and [InheritedWidget.ShouldRebuildDependents] returns true, all registered dependents are notified and scheduled for rebuild.
Aspect\-Based Tracking
InheritedElement supports granular dependency tracking via aspects. When a dependent registers with a specific aspect (non-nil), it's stored in that dependent's aspect set. On update, if the widget implements AspectAwareInheritedWidget, ShouldRebuildDependent is called for each dependent to determine if it should rebuild based on its registered aspects.
Note: Aspect sets only grow during an element's lifetime. If a widget stops depending on an aspect across rebuilds, the old aspect remains registered. This may cause extra rebuilds but is safe (over-notification, not under).
type InheritedElement struct {
// contains filtered or unexported fields
}
func NewInheritedElement
func NewInheritedElement() *InheritedElement
NewInheritedElement creates an InheritedElement. The widget and build owner are set later by the framework during inflation.
func (*InheritedElement) AddDependent
func (e *InheritedElement) AddDependent(dependent Element, aspect any)
AddDependent registers an element as depending on this inherited widget. If aspect is non-nil, it's added to the dependent's aspect set for granular tracking. If aspect is nil, a sentinel is added indicating the widget depends on all changes.
Note: Aspect sets only grow during an element's lifetime. If a widget changes which aspects it depends on across rebuilds, old aspects remain registered. This may cause extra rebuilds but is safe (over-notification, not under-notification).
func (*InheritedElement) Mount
func (e *InheritedElement) Mount(parent Element, slot any)
func (*InheritedElement) MountWithSelf
func (e *InheritedElement) MountWithSelf(parent Element, slot any, self Element)
MountWithSelf allows a wrapper element to specify itself as the parent for children.
func (*InheritedElement) RebuildIfNeeded
func (e *InheritedElement) RebuildIfNeeded()
func (*InheritedElement) RebuildIfNeededWithSelf
func (e *InheritedElement) RebuildIfNeededWithSelf(self Element)
RebuildIfNeededWithSelf allows a wrapper element to specify itself as the parent.
func (*InheritedElement) RemoveDependent
func (e *InheritedElement) RemoveDependent(dependent Element)
RemoveDependent unregisters an element as depending on this inherited widget.
func (*InheritedElement) RenderObject
func (e *InheritedElement) RenderObject() layout.RenderObject
RenderObject returns the render object from the child element.
func (*InheritedElement) Unmount
func (e *InheritedElement) Unmount()
func (*InheritedElement) Update
func (e *InheritedElement) Update(newWidget Widget)
func (*InheritedElement) VisitChildren
func (e *InheritedElement) VisitChildren(visitor func(Element) bool)
type InheritedProvider
InheritedProvider is a generic inherited widget that eliminates boilerplate for simple data-down-the-tree patterns.
Example usage:
// Provide a value
core.InheritedProvider[*User]{
Value: currentUser,
Child: MainContent{},
}
// Consume from anywhere in the subtree
if user, ok := core.Provide[*User](ctx); ok {
// use user
}
For custom comparison logic, set ShouldRebuild:
core.InheritedProvider[*User]{
Value: currentUser,
Child: MainContent{},
ShouldRebuild: func(old, new *User) bool {
return old.ID != new.ID // Only rebuild on ID change
},
}
Note: The default comparison uses == which panics for non-comparable types (slices, maps, funcs). For these types, you must provide a ShouldRebuild function.
For more advanced use cases like aspect-based tracking, implement a custom InheritedWidget instead.
type InheritedProvider[T any] struct {
// Value is the data to provide to descendants.
Value T
// Child is the child widget tree.
Child Widget
// WidgetKey is an optional key for widget identity.
WidgetKey any
// ShouldRebuild is an optional function to customize when dependents rebuild.
// If nil, defaults to value inequality (any(old) != any(new)).
// Required for non-comparable types (slices, maps, funcs) to avoid panics.
ShouldRebuild func(old, new T) bool
}
func (InheritedProvider[T]) ChildWidget
func (p InheritedProvider[T]) ChildWidget() Widget
ChildWidget implements InheritedWidget.
func (InheritedProvider[T]) CreateElement
func (p InheritedProvider[T]) CreateElement() Element
CreateElement implements Widget.
func (InheritedProvider[T]) Key
func (p InheritedProvider[T]) Key() any
Key implements Widget.
func (InheritedProvider[T]) ShouldRebuildDependents
func (p InheritedProvider[T]) ShouldRebuildDependents(oldWidget InheritedWidget) bool
ShouldRebuildDependents implements InheritedWidget. Returns true if dependents should rebuild when this widget is updated.
type InheritedWidget
InheritedWidget provides data to descendant widgets without explicit parameter passing through the widget tree.
InheritedWidget is the foundation for dependency injection in the widget tree. Descendant widgets can access the inherited data via [BuildContext.DependOnInherited], and they automatically rebuild when the inherited data changes.
When to Use
Use InheritedWidget when data needs to be accessed by many widgets at different levels of the tree. Common examples include:
- Theme data (colors, typography)
- Localization strings
- User authentication state
- Application configuration
Simple Usage with InheritedProvider
For simple cases, use the generic InheritedProvider which eliminates boilerplate:
// Provide data
core.InheritedProvider[*UserState]{
Value: userState,
Child: MyApp{},
}
// Access data (in a descendant's Build method)
if user, ok := core.Provide[*UserState](ctx); ok {
// use user
}
Custom Implementation
Embed InheritedBase and implement only ShouldRebuildDependents:
type UserScope struct {
core.InheritedBase
User *User
}
func (u UserScope) ShouldRebuildDependents(old core.InheritedWidget) bool {
return u.User != old.(UserScope).User
}
When [ShouldRebuildDependents] returns true, all dependents are notified. For fine-grained per-dependent filtering based on registered aspects, implement the optional AspectAwareInheritedWidget interface.
type InheritedWidget interface {
Widget
// ChildWidget returns the child widget tree.
ChildWidget() Widget
// ShouldRebuildDependents returns true if dependents should rebuild when this
// widget is updated. When true, all dependents are notified unless the widget
// also implements [AspectAwareInheritedWidget] for per-dependent filtering.
ShouldRebuildDependents(oldWidget InheritedWidget) bool
}
type LayoutBuilderElement
LayoutBuilderElement hosts a LayoutBuilderWidget, deferring child building to the layout phase so the builder function receives actual constraints.
This element uses a dual-trigger invalidation model:
- Layout-phase trigger: when the parent's constraints change, the render object calls layoutCallback during PerformLayout and the element re-invokes the builder with the new constraints.
- Build-phase trigger: when an inherited dependency changes or the widget is updated, LayoutBuilderElement.RebuildIfNeeded translates the dirty flag into childDirty and calls MarkNeedsLayout on the render object, which schedules a new layout pass that re-invokes the builder.
LayoutBuilderElement implements [renderObjectHost], allowing descendant RenderObjectElements to attach their render objects through it.
type LayoutBuilderElement struct {
// contains filtered or unexported fields
}
func NewLayoutBuilderElement
func NewLayoutBuilderElement(widget LayoutBuilderWidget, owner *BuildOwner) *LayoutBuilderElement
NewLayoutBuilderElement creates a LayoutBuilderElement for the given widget. The owner may be nil during widget testing; it is set by the framework when the element is mounted into a live tree.
func (*LayoutBuilderElement) Mount
func (e *LayoutBuilderElement) Mount(parent Element, slot any)
Mount creates the render object, registers the layout callback on it, attaches to the render tree, and marks the child as needing its first build. Child building is deferred until the first layout pass.
func (*LayoutBuilderElement) RebuildIfNeeded
func (e *LayoutBuilderElement) RebuildIfNeeded()
RebuildIfNeeded handles build-phase invalidation (e.g. inherited dependency changes). Child building is still deferred to layout, but we must translate the dirty flag into childDirty + MarkNeedsLayout so the layout callback re-invokes the builder.
func (*LayoutBuilderElement) RenderObject
func (e *LayoutBuilderElement) RenderObject() layout.RenderObject
RenderObject returns the render object owned by this element.
func (*LayoutBuilderElement) Unmount
func (e *LayoutBuilderElement) Unmount()
Unmount recursively unmounts the child element and detaches the render object from the render tree.
func (*LayoutBuilderElement) Update
func (e *LayoutBuilderElement) Update(newWidget Widget)
Update replaces the widget, marks the child dirty, and triggers a relayout so the layout callback re-invokes the builder with the new widget's function.
func (*LayoutBuilderElement) UpdateSlot
func (e *LayoutBuilderElement) UpdateSlot(newSlot any)
UpdateSlot updates the slot and notifies the render parent of the move.
func (*LayoutBuilderElement) VisitChildren
func (e *LayoutBuilderElement) VisitChildren(visitor func(Element) bool)
VisitChildren calls the visitor with the single child element, if present.
type LayoutBuilderWidget
LayoutBuilderWidget is implemented by widgets that defer child building to the layout phase. Unlike standard widgets whose Build runs before layout, a LayoutBuilderWidget provides a builder function that is invoked during the render object's PerformLayout, once the parent's constraints are known.
The LayoutBuilder method returns the builder callback. The element stores this callback and passes it to the render object, which invokes it with the resolved constraints during layout.
type LayoutBuilderWidget interface {
RenderObjectWidget
LayoutBuilder() func(ctx BuildContext, constraints layout.Constraints) Widget
}
type Listenable
Listenable is an interface for types that support untyped change notification. Signal, Derived, and Notifier all satisfy this interface.
Listenable is the dependency type accepted by NewDerived and UseDerived. AddListener should return an unsubscribe function.
type Listenable interface {
AddListener(listener func()) func()
}