Startup-Validierung: Splash-Screen und StartupResult (Teil 33)
Eine Desktop-App, die gleichzeitig mit Online-APIs und lokalen Dateien arbeitet, muss beim Start wissen, was verfügbar ist. Ohne diese Prüfung passieren Fehler erst dann, wenn der Nutzer eine Funktion aufruft — und das fühlt sich unzuverlässig an. EchoPlay löst das mit einem StartupValidator, der während des Splash-Screens systematisch alle Abhängigkeiten prüft und das Ergebnis als einmaligen Zustandscontainer an die App weitergibt.
Der StartupValidator: Drei Prüfungen beim Start
Beim Splash-Screen führt der StartupValidator drei Prüfungen durch. Zuerst die Online-Verfügbarkeit: Ist der konfigurierte Provider — also Spotify oder Apple Music — erreichbar? Diese Prüfung wird nur ausgeführt, wenn die App nicht im Offline-Modus läuft und ein Provider konfiguriert ist. Dann die lokale Bibliothek: Existiert der konfigurierte Ordner auf der Festplatte? Und schließlich der Cache-Status: Muss der Neuerscheinungen-Cache neu aufgebaut werden, weil er veraltet ist oder der Nutzer einen Rebuild angefordert hat?
public sealed class StartupValidator : IStartupValidator
{
public async Task<StartupResult> ValidateAsync(Action<string>? onStatus = null)
{
AppSettings settings = await settingsService.GetAsync();
// Online-Check nur wenn nicht im Offline-Modus
if (!settings.OfflineMode && settings.ActiveProvider != ProviderType.None)
{
onStatus?.Invoke("Prüfe Online-Verbindung …");
isOnlineAvailable = await CheckOnlineAvailabilityAsync(settings);
}
// Lokale Bibliothek prüfen
if (settings.LocalLibraryEnabled
&& !string.IsNullOrWhiteSpace(settings.LocalLibraryRootPath))
{
onStatus?.Invoke("Prüfe lokale Bibliothek …");
isLocalAvailable = CheckLocalLibraryAccess(settings.LocalLibraryRootPath);
}
return new StartupResult(settings, isOnlineAvailable, isLocalAvailable, ...);
}
}
Zwei Dinge fallen hier auf. Erstens: Der Validator implementiert ein Interface (IStartupValidator), obwohl es nur eine Implementierung gibt. Das hat einen praktischen Grund — du kannst für Unit-Tests einen Fake einsetzen, der sofort ein vordefiniertes Ergebnis liefert, ohne tatsächlich Netzwerkverbindungen zu prüfen. Zweitens: Die Methode gibt ein StartupResult zurück, statt den Zustand irgendwo in eine globale Variable zu schreiben. Das macht den Datenfluss explizit und nachvollziehbar.
Splash-Screen mit Echtzeit-Statustext
Der onStatus-Parameter ist ein Callback vom Typ Action<string> — also eine Funktion, die einen Text entgegennimmt und nichts zurückgibt. Der Splash-Screen übergibt beim Aufruf eine anonyme Funktion, die den empfangenen Text auf der Oberfläche anzeigt. Jedes Mal, wenn der Validator einen neuen Prüfschritt beginnt, ruft er onStatus?.Invoke(...) auf, und der Splash-Screen aktualisiert sich.
// SplashWindow.xaml.cs
StartupResult result = await validator.ValidateAsync(status =>
{
DispatcherQueue.TryEnqueue(() => StatusText.Text = status);
});
Das DispatcherQueue.TryEnqueue ist dabei entscheidend: Der Validator läuft teilweise auf Hintergrund-Threads (wegen der Netzwerk-Prüfung), aber UI-Elemente dürfen nur vom UI-Thread aus geändert werden. Der DispatcherQueue schiebt die Textänderung zurück auf den UI-Thread. Ohne diesen Umweg würde die App mit einer Exception abstürzen. Warum Action<string> statt einem Event? Weil der Callback nur einmalig während des Starts gebraucht wird — ein Event wäre für diesen Zweck überdimensioniert.
StartupResult: Einmal-Daten vs. Live-Daten
Das Ergebnis der Validierung wird in App.StartupResultData zwischengespeichert. Das Dashboard liest es beim ersten Laden einmalig aus und zeigt den initialen Zustand an — welche Serien überwacht werden, wie viele Neuerscheinungen es gibt, ob Online-Dienste verfügbar sind. Danach werden alle Daten direkt aus der Datenbank geladen, weil sich Zustände wie IsWatched oder IsFavorite während der laufenden Session ändern können.
Diese Unterscheidung ist wichtig: Das StartupResult ist ein Snapshot — eine Momentaufnahme zum Zeitpunkt des Starts. Es taugt für die initiale Anzeige, aber nicht für laufende Aktualisierungen. Würde das Dashboard dauerhaft aus dem StartupResult lesen, würde es Änderungen verschlafen, die der Nutzer seit dem Start vorgenommen hat. Deshalb die Regel: Startup-Daten für den ersten Eindruck, Datenbank für alles Weitere.
Dependency Injection für Startup-Logik
Auch der StartupValidator wird über Dependency Injection aufgelöst — er ist kein statischer Helper, der irgendwo hart verdrahtet ist. Das bedeutet: Alle seine Abhängigkeiten (Settings-Service, Online-Prüfung, Cache-Logik) werden vom DI-Container injiziert. Das macht den Validator testbar und austauschbar. Wenn du morgen einen weiteren Check hinzufügen willst — zum Beispiel eine Versionsprüfung — änderst du nur den Validator und seine Registrierung, nicht den Splash-Screen-Code.
Die gezeigten Code-Beispiele dienen zur Veranschaulichung. Nutzung auf eigene Verantwortung. Mehr dazu