Stateful widgets in C#

Widgets are immutable, so mutable data lives one level down — in a State<T> object owned by the element rather than by the widget.

State<T> and SetState

A StatefulWidget is still an immutable description. What persists across rebuilds is the State<T> it creates. Calling SetState mutates your fields and marks the element dirty; the framework rebuilds it on the next frame.

Counter.cs
public sealed class Counter : StatefulWidget
{
    public override State CreateState() => new _CounterState();
}

internal sealed class _CounterState : State<Counter>
{
    private int _count;

    public override Widget Build(BuildContext context)
    {
        return new Column(
            children:
            [
                new Text($"Count: {_count}"),
                new TextButton(
                    onPressed: () => SetState(() => _count++),
                    child: new Text("Increment")
                )
            ]
        );
    }
}

Mutating a field without SetState changes your data but never schedules a rebuild — the same footgun as in Flutter, with the same fix.

Lifecycle hooks

Because the element decides whether to update or recreate, a State can outlive many widget instances. The hooks below tell you which is happening.

FeedState.cs
internal sealed class _FeedState : State<Feed>
{
    public override void InitState()
    {
        base.InitState();
        // Runs once, before the first Build.
    }

    public override void DidUpdateWidget(Feed oldWidget)
    {
        base.DidUpdateWidget(oldWidget);
        // The parent rebuilt with a new widget; this State survived.
        if (oldWidget.Url != Widget.Url) Reload();
    }

    public override void Dispose()
    {
        // Element is leaving the tree — release subscriptions here.
        base.Dispose();
    }
}
  • InitState — once per State, before the first build.
  • DidUpdateWidget — the parent rebuilt and supplied a new widget of the same type and key; compare old and new to decide what to refresh.
  • Dispose — the element is unmounted; release timers, subscriptions and handles here.

Hot reload keeps your state

Running under dotnet watch, an edit rebuilds the widget tree while existing State objects stay alive. Counters keep counting, scroll positions hold, and you land back on the same screen instead of restarting the app and navigating there again.

Sharing state down the tree

For data many widgets need — a theme, a locale, a signed-in user — InheritedWidget propagates values downward with dependency tracking, so only the elements that actually read the value rebuild when it changes. It is listed in the core widget catalog alongside the rest of the foundational set.