Value Semantics

SimpleAppState uses value semantics for all application state.

This means:

State is treated as values, not as mutable objects.

You do not change state by modifying objects. You change state by replacing values.

This page explains what value semantics means in practice and why SimpleAppState is designed this way.


The most important rule

If you remember only one rule, remember this:

Changing an object returned from get() never changes state.

Example:

final list = logs.get();
list.add('new entry'); // ❌ state is NOT updated

This is not a bug. It is the core design.


Why get() returns a copy

When you call get():

final value = slot.get();

the returned object is always a deep copy.

This prevents accidental changes such as:

  • modifying a list

  • editing a map

  • changing fields inside an object

Without this rule, state could change silently, without SimpleAppState noticing.

Silent changes are dangerous because:

  • widgets may not rebuild

  • undo / redo becomes unreliable

  • bugs become very hard to track

Returning a copy makes all state changes explicit.


How state should be updated

To change state, you must use set or update.

Example with set:

settings.set(
  AppSettings(theme: 'dark'),
);

Example with update:

logs.update((oldCopy) {
  oldCopy.add('new entry');
  return oldCopy;
});

In both cases:

  • a new value is created

  • the old value is replaced

  • SimpleAppState is notified of the change

This is the only way state can change.


Thinking in “before” and “after”

Value semantics encourages a simple mental model:

  • there is a previous value

  • you create a next value

  • the old one is discarded

You never think about:

  • “Who else is holding this object?”

  • “What happens if I mutate this list?”

Each update is a clear state transition.

This model is easy to reason about, especially for beginners.


Why this matters for widgets

Widgets rebuild when state changes.

If state could be mutated silently:

  • rebuilds might not happen

  • UI could become inconsistent

Because SimpleAppState controls all writes:

  • it always knows when a value changes

  • it knows exactly which slot changed

  • it can rebuild only the affected widgets

This is why rebuild behavior is predictable.


Value semantics and undo / redo

Undo and redo work by storing snapshots of state.

With value semantics:

  • each snapshot is a clean copy

  • no snapshot shares mutable objects

  • restoring a snapshot is safe

This makes features like:

  • undo / redo

  • persistence

  • time-travel debugging

much easier to implement correctly.


What values can be stored

Values stored in slots must be:

  • safely copyable, or

  • serializable

SimpleAppState validates values on set and update to ensure they are safe to store.

This prevents subtle bugs caused by non-copyable or externally mutable objects.

If you want to save a custom class, or a list or map that contains a custom class, use a class that extends CloneableFile in the file_state_manager package.


This may feel unfamiliar at first

If you are used to mutating objects directly, this design may feel strict.

However, it has important benefits:

  • fewer hidden bugs

  • clearer state flow

  • easier debugging

  • safer refactoring

After some time, many developers find it more comfortable than mutable state.


Summary

Value semantics means:

  • state is replaced, not mutated

  • get() always returns a copy

  • set and update are the only write paths

  • state changes are explicit and traceable

Once you accept this rule, SimpleAppState becomes much easier to understand.