Skip to content

Signals

Stefan Wurzelsand edited this page Jun 5, 2026 · 6 revisions

Signals

Einfacher Counter mit globalem Signal

import 'package:flutter/material.dart';
import 'package:signals_flutter/signals_flutter.dart';

final counter = signal(0);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Signals with Global Signal')),
        body: Center(
          child: SignalBuilder(builder: (context) => Text('Value: $counter')),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => counter.value++,
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Signals mit Service Locator GetIt

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:signals_flutter/signals_flutter.dart';

// --- DATA LAYER ---
class WeatherRepository {
  Future<String> fetchWeatherFromServer(String city) async {
    await Future.delayed(const Duration(seconds: 1));
    return '21°C, leicht bewölkt in $city';
  }
}

// --- LOGIC LAYER ---
class WeatherController {
  final WeatherRepository _repository;
  WeatherController(this._repository);

  final weatherData = signal<String?>(null);
  final isLoading = signal<bool>(false);

  Future<void> loadWeather(String city) async {
    isLoading.value = true;
    try {
      final result = await _repository.fetchWeatherFromServer(city);
      weatherData.value = result;
    } catch (e) {
      weatherData.value = 'Fehler beim Laden';
    } finally {
      isLoading.value = false;
    }
  }
}

// --- DEPENDENCY INJECTION ---
final di = GetIt.instance;

void setupArchitecture() {
  di.registerLazySingleton(() => WeatherRepository());
  di.registerLazySingleton(() => WeatherController(di<WeatherRepository>()));
}

// --- MAIN START ---
void main() {
  // Zwingt die Flutter-Engine zur korrekten Initialisierung vor dem Isolat-Start
  WidgetsFlutterBinding.ensureInitialized();

  // 1. Diese Zeile schaltet die automatischen Konsolen-Logs von Signals ab:
  // SignalsObserver.instance = null;

  // 2. (Optional) Wenn du auch das Tracking für die Flutter DevTools deaktivieren willst:
  // signalsDevToolsEnabled = false;

  setupArchitecture();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: WeatherScreen());
  }
}

// --- PRESENTATION LAYER (UI) ---
class WeatherScreen extends StatelessWidget {
  const WeatherScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final controller = di<WeatherController>();

    return Scaffold(
      appBar: AppBar(title: const Text('Wetter App (Signals & GetIt)')),
      body: Center(
        // Hier ist der korrekte SignalBuilder
        child: SignalBuilder(
          builder: (context) {
            if (controller.isLoading.value) {
              return const CircularProgressIndicator();
            }

            return Text(
              controller.weatherData.value ?? 'Noch keine Daten geladen.',
              style: const TextStyle(fontSize: 20),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.loadWeather('Dresden'),
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

Riverpod Beispiel mit Signals umgesetzt

signals_instead_riverpod_providers.dart

Die 5 Regeln für das State-Management mit GetIt + Signals

Beim Einsatz von GetIt (als Service-Locator) in Kombination mit Signals (für reaktive Zustände) kann die Architektur einer App bei unbedachtem Vorgehen schnell unübersichtlich werden. Die folgenden fünf Regeln dienen als Leitplanken, um deinen Code sauber, performant und frei von Memory Leaks zu halten.

  1. Nutze .readonly() für den Export
    Mach alle Signals in deinen GetIt-Services private und biete der UI nur ein ReadonlySignal an. Das zwingt dich und dein Team dazu, die verändernde Logik direkt im Service zu kapseln, anstatt sie über die gesamte App zu verstreuen.

  2. Widgets lösen nur Aktionen aus
    Ein Widget sollte niemals Business-Logik berechnen oder Werte direkt in GetIt-Instanzen überschreiben. Das Widget agiert rein als Konsument und Signalgeber; es sagt dem Service lediglich über explizite Methoden, was zu tun ist (z. B. userService.login(...) oder cartService.addItem(...)).

  3. Vermeide, dass Signals andere Signals außerhalb ihres Bereichs mutieren
    Ein effect in Service A sollte nicht das Signal in Service B direkt verändern. Wenn Service B von den Daten aus Service A abhängt, nutze dafür lieber ein computed Signal, welches die Abhängigkeit deklarativ und automatisch auflöst.

  4. Achte auf den Lifecycle bei kurzlebigen Instanzen
    Wenn du computed Signals oder effects in temporären Factory- oder Scoped-Instanzen (oder direkt in Widgets) erstellst, die von globalen GetIt-Singletons abhängen, hält das Singleton eine Referenz auf diese Instanz. Du musst sie beim Schließen zwingend via .dispose() aufräumen, um Memory Leaks zu verhindern.

  5. Bündele zusammenhängende Updates mit batch()
    Wenn eine einzige Benutzeraktion mehrere Signals gleichzeitig verändert – selbst wenn diese über verschiedene GetIt-Services verteilt sind –, wickle die Aufrufe in ein batch() ein. Das verhindert unruhige Zwischenzustände (Glitches) sowie doppelte, ressourcenfressende Berechnungen in nachgelagerten Elementen und deiner UI.

Clone this wiki locally