George Pashev

Website of George Pashev (Jidai Mirai)

Scientist, Programmer, Data Scientist, Entrepreneur

AutoSignals - the new programming paradigm

AutoSignal System - User Guide

Jidai Mirai - George Pashev

Overview

The AutoSignal system is a powerful Dart based tool for automatic component connection through signal-slot architecture. It allows objects to communicate with each other without direct references, making code more modular and maintainable.

Core Concepts

Signal

  • Definition: An event that can be emitted by an object
  • Purpose: Notifies other objects about state changes
  • Examples: contactAdded, contactsLoaded, databaseError

Slot

  • Definition: A method that processes a specific type of signal
  • Purpose: Reacts to events from other objects
  • Examples: _onContactAdded, _onContactsLoaded, _onDatabaseError

Auto-Discovery

  • Definition: System that automatically connects signals to slots
  • Principle: Based on name and data type matching

Creating AutoSignal Objects

Step 1: Inherit from AutoSignalObject

class MyClass extends AutoSignalObject {
  MyClass() : super() {
    // Register in namespace (optional)
    AutoDiscoveryRegistry().registerToNamespace(this, 'myNamespace');
    
    // Register signals and slots
    _setupSignalsAndSlots();
  }
}

Step 2: Register Signals

void _setupSignalsAndSlots() {
  // Register signals with the data types they will emit
  registerSignal<Contact>('contactAdded', Contact);
  registerSignal<List<Contact>>('contactsLoaded', List<Contact>);
  registerSignal<String>('errorOccurred', String);
  registerSignal<bool>('operationCompleted', bool);
}

Step 3: Register Slots

void _setupSignalsAndSlots() {
  // ... signals ...
  
  // Register slots with handler functions
  registerSlot<Contact>('contactAdded', Contact, _onContactAdded);
  registerSlot<List<Contact>>('contactsLoaded', List<Contact>, _onContactsLoaded);
  registerSlot<String>('errorOccurred', String, _onErrorOccurred, priority: 1);
}

// Handler functions
void _onContactAdded(Contact contact) {
  print('New contact added: ${contact.name}');
  // Processing logic...
}

void _onContactsLoaded(List<Contact> contacts) {
  print('Loaded ${contacts.length} contacts');
  // Update UI or state...
}

void _onErrorOccurred(String error) {
  print('Error: $error');
  // Show error to user...
}

Emitting Signals

Basic Syntax

void someOperation() {
  try {
    // Perform operation...
    Contact newContact = createContact();
    
    // Emit signal with data
    emitSignal<Contact>('contactAdded', newContact);
    
  } catch (e) {
    // Emit error signal
    emitSignal<String>('errorOccurred', e.toString());
  }
}

Examples from Code

// In DatabaseManager
Future<Contact> createContact(Contact contact) async {
  // ... database operation ...
  final newContact = contact.copyWith(id: id);
  
  // Automatically notify all listeners
  emitSignal<Contact>('contactAdded', newContact);
  return newContact;
}

// In ContactManager  
void _onContactAdded(Contact contact) {
  _contacts.add(contact);
  _contacts.sort((a, b) => a.name.compareTo(b.name));
  
  // Forward signal up to UI
  emitSignal<List<Contact>>('contactsChanged', _contacts);
}

Automatic Connection

Matching Principles

  1. Signal Name = Slot Name

    // Signal: 'contactsLoaded'
    // Slot: 'contactsLoaded' ✅
    
  2. Convention with _on prefix

    // Signal: 'contactsLoaded'  
    // Slot: '_onContactsLoaded' ✅
    
  3. Data Type Matching

    // Signal: registerSignal<Contact>('contactAdded', Contact)
    // Slot: registerSlot<Contact>('contactAdded', Contact, handler) ✅
    

Activating Connections

// In main function or during initialization
void initializeApp() {
  // Create objects
  final databaseManager = DatabaseManager();
  final contactManager = ContactManager();
  final uiController = UIController();
  
  // Connect all objects in namespace
  AutoDiscoveryRegistry().connectNamespace('contacts');
}

Namespaces

Purpose

  • Grouping related objects
  • Control over which objects connect automatically
  • Organization of complex applications

Usage

class DatabaseManager extends AutoSignalObject {
  DatabaseManager() : super() {
    // Register in namespace
    AutoDiscoveryRegistry().registerToNamespace(this, 'contacts');
  }
}

class ContactManager extends AutoSignalObject {
  ContactManager() : super() {
    // Same namespace
    AutoDiscoveryRegistry().registerToNamespace(this, 'contacts');
  }
}

// Connect all objects in namespace
AutoDiscoveryRegistry().connectNamespace('contacts');

Slot Priorities

Basic Principle

Slots with higher priority execute first.

// Critical operations - high priority
registerSlot<String>('errorOccurred', String, _handleCriticalError, priority: 10);

// Regular operations - medium priority  
registerSlot<Contact>('contactAdded', Contact, _updateUI, priority: 5);

// Logging - low priority
registerSlot<Contact>('contactAdded', Contact, _logOperation, priority: 1);

Resource Cleanup

Automatic Cleanup

class MyClass extends AutoSignalObject {
  @override
  void dispose() {
    // AutoSignalObject automatically:
    // - Clears all signal handlers
    // - Unregisters object from registry
    // - Breaks all connections
    
    super.dispose(); // IMPORTANT: Always call super.dispose()
  }
}

In Flutter Widget

class _MyWidgetState extends State<MyWidget> {
  late MyClass _myObject;
  
  @override
  void initState() {
    super.initState();
    _myObject = MyClass();
  }
  
  @override
  void dispose() {
    _myObject.dispose(); // Clean up AutoSignal object
    super.dispose();
  }
}

Practical Examples

1. Simple Status Signal

class StatusManager extends AutoSignalObject {
  StatusManager() : super() {
    registerSignal<String>('statusChanged', String);
  }
  
  void updateStatus(String newStatus) {
    emitSignal<String>('statusChanged', newStatus);
  }
}

class UIController extends AutoSignalObject {
  UIController() : super() {
    registerSlot<String>('statusChanged', String, _onStatusChanged);
  }
  
  void _onStatusChanged(String status) {
    // Update UI
    statusBar.text = status;
  }
}

2. Complex Workflow

class DataLoader extends AutoSignalObject {
  DataLoader() : super() {
    registerSignal<bool>('loadingStarted', bool);
    registerSignal<List<Data>>('dataLoaded', List<Data>);
    registerSignal<String>('loadingError', String);
  }
  
  Future<void> loadData() async {
    emitSignal<bool>('loadingStarted', true);
    
    try {
      final data = await fetchFromAPI();
      emitSignal<List<Data>>('dataLoaded', data);
    } catch (e) {
      emitSignal<String>('loadingError', e.toString());
    }
  }
}

class ProgressIndicator extends AutoSignalObject {
  ProgressIndicator() : super() {
    registerSlot<bool>('loadingStarted', bool, _onLoadingStarted);
    registerSlot<List<Data>>('dataLoaded', List<Data>, _onDataLoaded);
  }
  
  void _onLoadingStarted(bool isLoading) {
    showSpinner(isLoading);
  }
  
  void _onDataLoaded(List<Data> data) {
    hideSpinner();
  }
}

3. Error Handling System

class ErrorManager extends AutoSignalObject {
  ErrorManager() : super() {
    registerSlot<String>('databaseError', String, _onDatabaseError, priority: 10);
    registerSlot<String>('networkError', String, _onNetworkError, priority: 9);
    registerSlot<String>('generalError', String, _onGeneralError, priority: 1);
    
    registerSignal<ErrorInfo>('errorProcessed', ErrorInfo);
  }
  
  void _onDatabaseError(String error) {
    final errorInfo = ErrorInfo(
      type: ErrorType.database,
      message: error,
      severity: ErrorSeverity.critical
    );
    
    logError(errorInfo);
    emitSignal<ErrorInfo>('errorProcessed', errorInfo);
  }
}

Debugging and Monitoring

Debug Information

void printDebugInfo() {
  // Registry information
  AutoDiscoveryRegistry().printDebugInfo();
  
  // Object information
  print('Signals: ${getSignals().keys}');
  print('Slots: ${getSlots().keys}');
}

Signal Tracing

void emitSignal<T>(String name, T value) {
  print('Signal emitted: $name = $value');
  super.emitSignal<T>(name, value);
}

Best Practices

1. Naming

  • Signals: Use verb forms (contactAdded, dataLoaded)
  • Slots: Use _on prefix (_onContactAdded, _onDataLoaded)

2. Data Types

  • Always specify concrete types
  • Avoid dynamic except in special cases
  • Use custom classes for complex data

3. Lifecycle Management

  • Always call dispose()
  • Check isDisposed before operations
  • Use try-catch in slot handlers

4. Performance

  • Avoid heavy operations in slot handlers
  • Use priorities for critical operations
  • Consider async slots for I/O operations

5. Architecture

  • Group related objects in namespaces
  • Create clear separation of concerns
  • Document signal-slot contracts

Conclusion

The AutoSignal system provides a powerful and flexible way to create loosely-coupled architectures. It is especially useful for:

  • Flutter applications with complex state management
  • Event-driven architectures
  • Plugin systems
  • Modular applications

 

Following this guide, you will be able to create robust and maintainable applications with clean architecture and automatic dependency management.

For updated sources, contact me by e-mail.

fulltext

Keywords

contactstringvoiddatasignalsuperregisterslotlistemitsignalpriorityautosignalobjectcontactaddedslotcontactsregistersignalclassextendsobjectsnameslots