RefAppState

RefAppState is an alternative state model for SimpleAppState that works with reference values instead of deep-copied values.

It is designed for applications that need to manage large, mutable, or non-serializable objects such as:

  • 3D or 2D scene graphs (e.g. sp3d data)

  • vector graphics and shape trees

  • large in-memory document or model structures

  • engine-level objects that are expensive to clone

This page explains how RefAppState differs from SimpleAppState and when it should be used.


What RefAppState owns

A RefAppState instance owns:

  • all application state references

  • RefSlot definitions and their types

  • listeners and batch update coordination

It does not own:

  • widgets

  • UI-local objects (controllers, animations, focus nodes)

  • rendering lifecycle

As with SimpleAppState, state lifetime and ownership are kept explicit and predictable.


Reference semantics instead of value semantics

The fundamental difference from SimpleAppState is how values are stored.

SimpleAppState:

  • always stores deep-copied values

  • enforces value semantics

  • prevents accidental mutation

RefAppState:

  • stores references

  • does not deep-copy on get, set, or update

  • allows direct mutation of stored objects

This makes RefAppState much faster and more flexible, but also more dangerous if misused.

It should only be used when value semantics are impractical.


Creating a RefAppState

A RefAppState is created just like SimpleAppState:

final refState = RefAppState();

It is usually defined as a global or top-level variable, often in ui/ref_state.dart.

lib/
├── ui/
│   ├── app_state.dart
│   ├── ref_state.dart # If you use RefAppState, add ref_state.dart.
│   └── pages/
│       └── counter_page.dart  # UI Widgets
└── main.dart               # Entry point

RefSlot instead of StateSlot

RefAppState does not use StateSlot<T>.

Instead, it provides RefSlot<T>:

final model = refState.slot<MyModel>('model', initial: MyModel());

RefSlot<T> behaves like StateSlot<T>, but with reference semantics.

When you read a value:

final m = model.get();

You receive the actual stored object, not a copy.

Mutating it will immediately affect the state:

m.vertices.add(...);   // modifies state directly

This is intentional.


Updating reference values

RefSlot.update receives the current reference:

model.update((m) {
  m.recalculate();
  return m;
});

The returned object is stored as-is. No copying is performed.

This allows efficient in-place updates of large or complex objects.


Safety trade-offs

Because RefAppState uses references:

  • accidental mutation can corrupt state

  • equality comparisons are not structural

Therefore:

RefAppState should be treated as a low-level, performance-oriented state model.

It is ideal for:

  • geometry editors

  • design tools

  • large document models

It is not recommended for ordinary UI state.


Snapshots and undo

Although normal reads and writes use references, RefAppState supports deep-copy snapshots for undo and restore.

final snapshot = refState.clone();

A cloned RefAppState:

  • deep-copies all stored data

  • contains no listeners or UI bindings

  • is safe to store in undo stacks

Important: All values stored in a RefAppState must still be deep-copyable in order to use clone or replaceDataFrom.

Only the following kinds of objects are supported:

  • primitive types (int, double, bool, String)

  • JSON-serializable structures (Map, List, etc)

  • classes that implement CloneableFile

If any non-copyable object is stored in a slot, calling clone or replaceDataFrom will throw a runtime error.

This is intentional: it ensures that undo / redo and persistence remain correct and deterministic.

To restore:

refState.replaceDataFrom(snapshot);

This replaces only the stored data, while preserving:

  • slot definitions

  • widget subscriptions

  • runtime listeners

This model is optimized for large mutable objects: Copy costs are only incurred when creating or restoring a snapshot.


Batch updates

RefAppState supports batch updates in the same way as SimpleAppState:

refState.batch(() {
  model.set(newModel);
  // update other RefSlots
});

Batching controls listener notifications and widget rebuilds, but does not affect reference semantics.


What RefAppState is not

RefAppState is not:

  • safe against accidental mutation

  • suitable for small UI state

  • a drop-in replacement for SimpleAppState

It is a specialized tool for large, mutable, performance-critical state.

Use it only when value semantics are too expensive or impossible.


API

RefAppState