State Management¶
#-CAD uses the Provider package for state management, with state split across four main classes that handle different concerns.
Architecture Overview¶
// In main.dart
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) {
final designState = DesignState();
designState.initializeUndoStack(); // Ensures undo stack starts with the initial state
designState.loadAssemblyHandleColors(); // Load persisted handle colors
return designState;
},
),
ChangeNotifierProvider(create: (context) => ActionState()),
ChangeNotifierProvider(create: (context) => ServerState()),
ChangeNotifierProvider(create: (context) => UpdateState()),
],
)
DesignState¶
File: lib/app_management/shared_app_state.dart
The central state container for all design data. Uses mixin composition for modularity.
Mixin Architecture¶
class DesignState extends ChangeNotifier
with DesignStateContract,
DesignStateCoreMixin,
DesignStateFileIOMixin,
DesignStateLayerMixin,
DesignStateSlatMixin,
DesignStateHandleMixin,
DesignStateSlatColorMixin,
DesignStatePhantomMixin,
DesignStateCargoMixin,
DesignStateSeedMixin,
DesignStatePlateMixin,
DesignStateHandleLinkMixin,
DesignStateGroupingMixin {
// ...
}
Key Mixins¶
| Mixin | File | Responsibility |
|---|---|---|
DesignStateContract |
design_state_contract.dart |
Interface for inter-mixin communication |
DesignStateCoreMixin |
design_state_core_mixin.dart |
Basic state initialization |
DesignStateFileIOMixin |
design_state_file_io_mixin.dart |
Excel import/export |
DesignStateLayerMixin |
design_state_layer_mixin.dart |
Multi-layer management |
DesignStateSlatMixin |
design_state_slat_mixin.dart |
Slat create/read/update/delete operations |
DesignStateHandleMixin |
design_state_handle_mixin.dart |
Handle assignment |
DesignStateSlatColorMixin |
design_state_slat_color_mixin.dart |
Slat color customization |
DesignStatePhantomMixin |
design_state_phantom_mixin.dart |
Phantom slat management |
DesignStateCargoMixin |
design_state_cargo_mixin.dart |
Cargo placement |
DesignStateSeedMixin |
design_state_seed_mixin.dart |
Seed geometry |
DesignStatePlateMixin |
design_state_plate_mixin.dart |
DNA plate management |
DesignStateHandleLinkMixin |
design_state_handle_link_mixin.dart |
Handle linking |
DesignStateGroupingMixin |
design_state_grouping_mixin.dart |
Slat grouping configurations |
Example Core Data Structures¶
// Slat dictionary - keyed by unique slat ID
Map<String, Slat> slats;
// Layer information
String selectedLayerKey; // Current layer key (e.g., 'A', 'B')
String nextLayerKey; // Next available layer key
// Undo/redo via SlatUndoStack
SlatUndoStack undoStack; // Manages DesignSaveState snapshots
ActionState¶
File: lib/app_management/action_state.dart
Manages UI state, display preferences, and user action modes.
Key Properties¶
class ActionState extends ChangeNotifier {
// Panel/mode selection (0=slats, 1=assembly, 2=cargo, 3=settings)
int panelMode;
// Mode strings for each panel
String slatMode; // 'Add', 'Delete', etc.
String cargoMode;
String assemblyMode;
// Display toggles
bool displayGrid;
bool displayAssemblyHandles;
bool displayCargoHandles;
bool displaySlatIDs;
bool displaySeeds;
bool displayBorder;
bool viewPhantoms;
// UI state
double splitScreenDividerWidth;
bool evolveMode;
bool threeJSViewerActive;
bool isSideBarCollapsed;
// Echo export settings
Map<String, dynamic> echoExportSettings;
//....
}
Usage¶
// Switch panels
actionState.setPanelMode(1); // Switch to assembly panel
// Toggle display options
actionState.setGridDisplay(true);
actionState.setAssemblyHandleDisplay(true);
// Update mode within a panel
actionState.updateSlatMode('Delete');
// Check current panel
if (actionState.panelMode == 0) {
// Handle slat panel interactions
}
ServerState¶
File: lib/app_management/server_state.dart
Handles gRPC communication with the Python evolution server.
Key Properties¶
class ServerState extends ChangeNotifier {
// gRPC clients
CrisscrossClient? hammingClient;
HealthClient? healthClient;
// Connection state
bool serverActive;
String statusIndicator; // 'BACKEND INACTIVE', 'IDLE', 'RUNNING', 'PAUSED'
// Evolution parameters (stored as strings)
Map<String, String> evoParams; // mutation_rate, evolution_generations, etc.
// Evolution progress
bool evoActive;
List<double> hammingMetrics;
List<double> physicsMetrics;
}
Server Communication¶
// Launch gRPC clients
serverState.launchClients(50055);
// Start evolution
serverState.evolveAssemblyHandles(slatArray, slatCoords, handleArray, ...);
// Pause/stop evolution
serverState.pauseEvolve();
List<List<List<int>>> result = await serverState.stopEvolve();
// Health check
await serverState.startupServerHealthCheck();
UpdateState¶
File: lib/app_management/update_state.dart
Manages app update checking and notification.
Key Properties¶
class UpdateState extends ChangeNotifier {
// Update status
UpdateStatus _status; // idle, checking, available, error
ReleaseInfo? _latestRelease;
String? _errorMessage;
// Computed properties
bool get updateAvailable;
bool get isChecking;
String get installPath;
}
enum UpdateStatus {
idle,
checking,
available,
error,
}
// ...
Usage¶
// Check for updates (silent respects hourly interval)
bool hasUpdate = await updateState.checkForUpdates(silent: true);
// Manual check (ignores interval)
bool hasUpdate = await updateState.checkForUpdates(silent: false);
// Skip current version
await updateState.skipCurrentVersion();
// Dismiss update dialog
updateState.dismiss();
// ...
Inter-State Communication¶
States can reference each other when needed:
// In a widget
final designState = context.read<DesignState>();
final serverState = context.read<ServerState>();
final actionState = context.read<ActionState>();
// Example: check if evolution mode is active
if (actionState.evolveMode && serverState.evoActive) {
// Evolution is running
}
// Access design data for server operations
final slats = designState.slats;
Best Practices¶
Updating State¶
// Always call notifyListeners() after changes
// the below functions are fictitious examples:
void addSlat(Slat slat) {
slats[slat.id] = slat;
notifyListeners(); // Triggers UI rebuild
}
// Batch updates for performance
void batchUpdate(List<Slat> newSlats) {
for (final slat in newSlats) {
slats[slat.id] = slat;
}
notifyListeners(); // Single notification
}
Undo/Redo¶
The app uses SlatUndoStack which manages DesignSaveState snapshots:
// SlatUndoStack manages history internally
class SlatUndoStack {
final List<DesignSaveState> _history = [];
int _currentIndex = -1;
static const int _maxHistory = 50;
void saveState(DesignSaveState state);
DesignSaveState? undo();
DesignSaveState? redo();
bool get canUndo;
bool get canRedo;
}
You should save the state after any operation that modifies the design:
void saveUndoState() {
undoStack.saveState(DesignSaveState(
slats: slats,
occupiedGridPoints: occupiedGridPoints,
layerMap: layerMap,
layerMetaData: {
'selectedLayerKey': selectedLayerKey,
'nextLayerKey': nextLayerKey,
'nextColorIndex': nextColorIndex,
},
cargoPalette: cargoPalette,
occupiedCargoPoints: occupiedCargoPoints,
seedRoster: seedRoster,
phantomMap: phantomMap,
assemblyLinkManager: assemblyLinkManager,
gridMode: gridMode));
}