Common Mistakes =============== This section lists common mistakes made by new users of SimpleAppState. Each mistake is explained together with the correct mental model. ---- Treating state as widget-owned ------------------------------ **Mistake** Defining **SimpleAppState** or slots inside widgets: .. code-block:: dart class MyWidget extends StatelessWidget { final appState = SimpleAppState(); // ❌ } **Why this is a problem** Widgets in Flutter are ephemeral. They can be rebuilt, recreated, or disposed at any time. Placing application state inside widgets makes state lifetime hard to reason about and breaks persistence and undo/redo use cases. **Correct approach** Define application state at a higher level, typically as a global or top-level object: .. code-block:: dart final appState = SimpleAppState(); final count = appState.slot('count', initial: 0); Widgets should *subscribe* to state, not own it. ---- Mutating values returned from slots ------------------------------------- **Mistake** Assuming that modifying a value returned from **get()** will update application state: .. code-block:: dart final list = logs.get(); list.add('new entry'); // ❌ has no effect on the original state **Why this is a problem** Values returned from **StateSlot.get()** are always **deep-copied**. This is a safety feature to prevent accidental side effects. Direct mutation of a value obtained via **get()** never affects the internal state of the slot, and therefore won't trigger UI rebuilds. **Correct approach** Always use **set()** or **update()**. Crucially, in SimpleAppState, the **old Value** passed to the **update** closure is **already a deep-copy**. This means you can safely mutate it directly and return it. You don't need to manually create a new List or Map instance. .. code-block:: dart // Correct and safe: // The 'oldCopy' list is already a copy, so you can mutate it directly. logs.update((oldCopy) { oldCopy.add('new entry'); return oldCopy; }); // Also works for custom objects: userSlot.update((user) { user.name = 'New Name'; return user; }); SimpleAppState ensures that these mutations remain local to the update process and do not leak into other parts of your application, keeping your code clean and predictable. ---- Forgetting to declare slot dependencies --------------------------------------- **Mistake** Accessing slot values in a widget without declaring them in **slots**: .. code-block:: dart class CounterView extends SlotStatefulWidget { @override List get slots => []; // ❌ } **Why this is a problem** SimpleAppState does not use implicit dependency tracking. If a widget does not declare a slot, it will not rebuild when that slot changes. **Correct approach** Explicitly list all slots the widget depends on: .. code-block:: dart @override List get slots => [count]; ---- Creating many small state objects --------------------------------- **Mistake** Creating multiple **SimpleAppState** instances for logically related application state. **Why this is a problem** Splitting state ownership too early makes it difficult to perform coordinated updates and persistence. **Correct approach** Start with a single **SimpleAppState** instance. Introduce multiple state objects only when you have clear ownership boundaries. A specific case where multiple SimpleAppStates instance are needed is when you want to separate the app state that is the target of Undo and Redo from the rest. ---- Mixing Import Types ------------------- **Mistake** Mixing relative imports and absolute package imports for the same file: .. code-block:: dart // In main.dart import 'package:my_app/ui/app_state.dart'; // In counter_page.dart import '../app_state.dart'; // ❌ Mixing with relative import **Why this is a problem** Dart treats absolute and relative imports as different libraries. This means the **appState** object in **main.dart** will be a **different instance** from the one in **counter_page.dart**. If you update the state in one file, the UI listening in the other file will not reflect the change, leading to extremely hard-to-debug issues. **Correct approach** Always use **absolute package imports** for any file that defines or uses your application state: .. code-block:: dart import 'package:my_app/ui/app_state.dart'; ---- Storing secrets in slots ------------------------- **Mistake** Storing confidential or security-sensitive data in **StateSlots**, or using such data as slot names: .. code-block:: dart // ❌ Bad: secrets should not be in SimpleAppState at all final password = appState.slot('user_password', initial: 'hunter2'); final apiKey = appState.slot('api_key', initial: 'sk-live-abcdef'); **Why this is a problem** Slots in **SimpleAppState** are designed for **application state**: - They have mandatory initial values - They participate in persistence and restore - They are tracked by undo/redo - They can be inspected by debug listeners - They may appear in logs, errors, and crash reports This means any value stored in a slot must be assumed to be: - Serializable - Debuggable - Potentially observable Secrets such as passwords, API keys, tokens, or private credentials violate these assumptions. Even if debug tools are disabled in production, storing secrets in state objects that are designed for persistence and inspection is fundamentally unsafe. **Correct approach** Do **not** store secrets in **SimpleAppState** at all. Use a dedicated secure storage mechanism instead, such as: - Platform keychains / keystores - Secure enclaves - OS-provided credential APIs - Encrypted storage services Slots should only contain data that is safe to: - Persist - Log - Restore - Debug If you need to reflect authentication or authorization status in your UI, store only **non-sensitive derived state** in slots: .. code-block:: dart // ✅ Safe: derived, non-secret state final isLoggedIn = appState.slot('is_logged_in', initial: false); final userId = appState.slot('user_id', initial: null); ---- Accessing slots from utility functions --------------------------------------- **Mistake** Accessing **StateSlot** or **SimpleAppState** directly from utility or helper functions: .. code-block:: dart // util.dart static void addLog(String message) { logs.update((oldCopy) { oldCopy.add(message); return oldCopy; }); } **Why this is a problem** Utility functions are, by definition, stateless. They should not own or mutate application state. When utilities access slots directly: - State mutations become hidden and hard to trace - Call sites no longer reveal which state is affected - Testing and reasoning about state flow becomes difficult This breaks one of the core principles of SimpleAppState: **all state access must be explicit and visible**. **Correct approach** Pass data *into* utilities, and return new values *out*. State updates should happen at the call site: .. code-block:: dart // util.dart static List addLog(List oldCopy, String message) { oldCopy.add(message); return oldCopy; } // call site logs.update((oldCopy) => addLog(oldCopy, message)); Utilities transform values. StateSlots own state. ---- Expecting context-based lookups ------------------------------- **Mistake** Assuming that widgets can implicitly access application state. **Why this is a problem** SimpleAppState intentionally avoids implicit or context-based state access. All state dependencies must be explicit and visible in code. This keeps rebuild behavior predictable and makes it clear which state changes can affect a given widget. **Correct approach** Pass or import the required **StateSlot** explicitly. If a widget rebuilds, you should always be able to point to the exact slot that caused it. ---- Updating state during build --------------------------- **Mistake** Updating application state during widget build: .. code-block:: dart @override Widget build(BuildContext context) { countSlot.update((v) => v + 1); // ❌ return const Text('Hello'); } **Why this is a problem** Widget build methods must be pure. They may run many times, and changing state during build can cause repeated rebuilds or unstable behavior. **Correct approach** Only update state in response to events (such as user actions or lifecycle callbacks). For a detailed explanation, see: :doc:`../flutter_integration/flutter_common_mistakes` ---- Next step --------------------------------- Congratulations! You’ve mastered the basics of **SimpleAppState**. From here, you can explore more advanced topics depending on your interests: - :doc:`../core_concepts/index` - :doc:`../state_models/index` - :doc:`../flutter_integration/index` - :doc:`../advanced/index` - :doc:`../project_management_and_design/index` Happy development!