ein buntes Themenspektrum

Monat: Juni 2025

Deine Flutter Wetter-App: Schritt für Schritt – Teil 6

Teil 6: Grünland im Blick – Die Grünlandtemperatursumme (GTS) berechnen und anzeigen

Hallo und herzlich willkommen zum sechsten und vorerst letzten Teil unserer Kernserie zur Flutter Wetter-App!

In den vorherigen Teilen haben wir eine Menge geschafft: von der Anzeige der aktuellen Temperatur über die Adresssuche bis hin zur Visualisierung des Temperaturverlaufs als Diagramm. Unsere App wird immer nützlicher!

Heute krönen wir das Ganze mit einer Spezialfunktion: der Berechnung und Anzeige der Grünlandtemperatursumme (GTS). Dieser agrarmeteorologische Wert ist besonders für Landwirte und Gärtner interessant, da er Aufschluss über den Vegetationsbeginn im Frühjahr gibt. Aber auch für Naturinteressierte ist es eine spannende Ergänzung.

Das Ziel für heute:

  • Wir verstehen, was die Grünlandtemperatursumme ist und wie sie berechnet wird.
  • Wir lernen, wie man historische Wetterdaten von einem anderen Endpunkt der Open-Meteo API (der Archiv-API) abruft.
  • Wir implementieren die Berechnungslogik für die GTS, inklusive der Monatsfaktoren.
  • Wir integrieren einen Caching-Mechanismus, um nicht bei jedem App-Start die gesamten historischen Daten neu laden zu müssen.
  • Wir erstellen ein neues Widget, um die GTS ansprechend darzustellen.

Warum GTS?

Die GTS ist ein gutes Beispiel dafür, wie man mit Flutter und externen APIs auch spezifischere, fachliche Anforderungen umsetzen kann. Es erfordert Datenabruf, Datenverarbeitung und eine klare Logik.

Schritt 1: Den Code für Teil 6 holen

Wie immer findest du den Code für diesen Teil im Git-Repository.

  1. Öffne ein Terminal in deinem Projektordner (flutter_weather_app_blog).
  2. Sichere lokale Änderungen, falls vorhanden.
  3. Wechsle zum main-Branch und aktualisiere:
    git checkout main
    git pull origin main

  4. Checke den Code-Stand für Teil 6 aus: (Tag-Name ggf. anpassen)
    git checkout part6-gts-calculation

  5. Abhängigkeiten holen & Code generieren:
    flutter pub get

    dart run build_runner build --delete-conflicting-outputs

Öffne das Projekt in VS Code.

Schritt 2: Was ist die Grünlandtemperatursumme (GTS)?

Die GTS ist die Summe der positiven Tagesmitteltemperaturen (oberhalb von 0°C), die ab Jahresbeginn anfallen. Dabei werden die Werte im Januar mit dem Faktor 0,5 und im Februar mit 0,75 gewichtet. Ab März zählt jeder positive Tagesmittelwert voll (Faktor 1,0). Ein bestimmter Schwellenwert der GTS (oft um 200 °Cd – Grad Celsius Tage) signalisiert den nachhaltigen Beginn der Vegetationsperiode.

Berechnung: GTS = Σ (Tagesmittel – 0°C) * Monatsfaktor (Nur für Tage mit Tagesmittel > 0°C)

Schritt 3: Die Datenquelle – Open-Meteo Archiv-API

Um die GTS zu berechnen, benötigen wir die täglichen Durchschnittstemperaturen seit dem 1. Januar des aktuellen Jahres. Die normale Forecast-API von Open-Meteo liefert historische Daten nur für einen begrenzten Zeitraum (z.B. die letzten 7-90 Tage). Für längere historische Zeitreihen bietet Open-Meteo eine separate Archiv-API.

  • Neue API-Endpunkte (lib/src/core/constants/app_constants.dart):
    • Wir haben Konstanten für die Basis-URL und den Endpunkt der Archiv-API hinzugefügt:
      static const String openMeteoArchiveApiBaseUrl = 'archive-api.open-meteo.com';
      static const String openMeteoArchiveEndpoint = '/v1/archive';

  • Parameter für GTS (lib/src/core/constants/app_constants.dart):
    • Ebenfalls neu sind die Konstanten für die GTS-Berechnung selbst:
      static const double gtsBaseTemperature = 0.0;
      static const Map<int, double> gtsMonthlyFactors = {1: 0.5, 2: 0.75};
      static const int gtsLocationCachePrecision = 2; // Für Caching

Schritt 4: Datenmodelle für historische Daten (data Layer Models)

Die Archiv-API liefert tägliche Daten in einer ähnlichen, aber doch eigenen Struktur. Daher brauchen wir neue Modelle:

  • lib/src/features/weather/data/models/daily_units_model.dart (NEU): Analog zum HourlyUnitsModel, aber für tägliche Daten (z.B. Einheit für temperature_2m_mean).
  • lib/src/features/weather/data/models/daily_data_model.dart (NEU): Bildet das daily-Objekt der Archiv-API ab. Enthält Listen für time (Datum-Strings) und temperature_2m_mean (tägliche Durchschnittstemperaturen). Die fromJson-Methode parst diese in DateTime– und double-Listen.
  • lib/src/features/weather/data/models/historical_response_model.dart (NEU): Das Gesamtmodell für die Antwort der Archiv-API, analog zum ForecastResponseModel.

Schritt 5: API-Service erweitern (data Layer Datasource)

Der WeatherApiService (lib/src/features/weather/data/datasources/weather_api_service.dart) wurde um eine Methode zum Abruf der historischen Daten erweitert:

  • getHistoricalDailyTemperatures (NEUE Methode):
    • Nimmt latitude, longitude, startDate und endDate als Parameter.
    • Baut die URL für den Archiv-API-Endpunkt (aus AppConstants).
    • Fordert daily=temperature_2m_mean an.
    • Parst die JSON-Antwort mithilfe des neuen HistoricalResponseModel.
    • Enthält robustes Error-Handling, ähnlich wie getForecastWeather.

Schritt 6: Der GTS-Berechnungsservice (application Layer)

Die eigentliche Logik zur Berechnung der GTS und das Caching der dafür benötigten Daten haben wir in einen neuen Service ausgelagert:

  • lib/src/features/weather/application/gts_calculator_service.dart (NEU):
    • Riverpod Provider: Wird über @riverpod bereitgestellt (gtsCalculatorServiceProvider) und bekommt den WeatherApiService injiziert.
    • Caching: Implementiert einen einfachen In-Memory-Cache (_historicalDataCache, _forecastForGtsCache).
      • _getLocationCacheKey: Erstellt einen eindeutigen Schlüssel für den Cache basierend auf gerundeten Koordinaten (um Ungenauigkeiten durch GPS zu minimieren).
      • Vor einem API-Aufruf wird geprüft, ob gültige Daten im Cache vorhanden sind (_cacheDuration).
    • calculateGtsForLocation(LocationInfo location) Methode:
      1. Cache-Prüfung: Versucht, historische Tagesmittelwerte aus dem Cache zu laden.
      2. API-Abruf (historisch): Wenn nicht im Cache, ruft es _apiService.getHistoricalDailyTemperatures auf, um die Tagesmittelwerte vom 1. Januar bis gestern zu holen. Speichert das Ergebnis im Cache.
      3. Datenaufbereitung: Die erhaltenen Daten werden in einer Map<DateTime, double> (Datum -> Temperatur) gespeichert.
      4. Forecast-Ergänzung (Lücke füllen): Die Archiv-API liefert oft Daten nur bis zum Vortag. Wenn die letzten Tage des aktuellen Jahres fehlen, um bis „gestern“ zu kommen (z.B. weil die Archivdaten noch nicht aktualisiert sind), versucht der Service, diese Lücke mit stündlichen Daten aus der Forecast-API zu füllen.
        • Dafür wird _apiService.getForecastWeather mit past_days (um die Lücke abzudecken) und forecast_days: 0 aufgerufen.
        • Aus den stündlichen Forecast-Daten werden dann Tagesmittelwerte für die fehlenden Tage berechnet und zu den dailyAverages hinzugefügt. Auch diese Forecast-Daten werden gecacht.
      5. GTS-Summation: Iteriert durch alle Tage des aktuellen Jahres (bis gestern) in der dailyAverages-Map.
        • Wenn die Tagesmitteltemperatur > gtsBaseTemperature (0°C) ist:
        • Der entsprechende gtsMonthlyFactor (Jan: 0.5, Feb: 0.75, sonst 1.0) wird angewendet.
        • Der Beitrag des Tages (Tagesmittel - Basis) * Faktor wird zur Gesamtsumme addiert.
      6. Gibt die berechnete gtsSum zurück.
    • Fehlerbehandlung: Wirft GtsCalculationException bei Problemen.

Schritt 7: Datenmodell und Repository anpassen (domain & data Layer)

  • WeatherData Entität (lib/src/features/weather/domain/entities/weather_data.dart):
    • Wurde um das Feld final double greenlandTemperatureSum; erweitert.
    • Die empty und copyWith Methoden wurden angepasst.
  • WeatherRepositoryImpl (lib/src/features/weather/data/repositories/weather_repository_impl.dart):
    • Abhängigkeit: Bekommt jetzt den GtsCalculatorService über den Konstruktor injiziert (dank Riverpod).
    • getWeatherForLocation-Methode:
      • Startet den Aufruf von _apiService.getForecastWeather() (für aktuelle und stündliche Daten) und _gtsCalculatorService.calculateGtsForLocation() parallel mit Futures.
      • Wartet mit await auf beide Ergebnisse.
      • Fängt Fehler von calculateGtsForLocation ab und gibt in diesem Fall double.nan für die GTS zurück, damit der Rest der Wetterdaten trotzdem angezeigt werden kann.
      • Fügt den erhaltenen gtsValue beim Erstellen des WeatherData-Objekts hinzu.
      • Kann jetzt auch GtsCalculationException fangen und in GtsFailure umwandeln.

Schritt 8: Anzeige der GTS (presentation Layer)

  • lib/src/features/weather/presentation/widgets/gts_display.dart (NEU):
    • Ein neues StatelessWidget, das den gtsValue als Parameter erhält.
    • Stellt die GTS in einer Card ansprechend dar, inklusive Icon und Einheit (°Cd).
    • Zeigt „–“ an, wenn der gtsValue double.nan ist (also ein Fehler bei der Berechnung auftrat).
  • lib/src/features/weather/presentation/screens/weather_screen.dart:
    • In der _buildSuccessContent-Methode wird nun das neue GtsDisplay-Widget hinzugefügt und bekommt data.greenlandTemperatureSum übergeben. Es wird zwischen der aktuellen Temperaturanzeige und dem Diagramm platziert.

Schritt 9: Änderungen am State Management (application Layer)

Überraschenderweise sind hier keine Änderungen im WeatherState oder WeatherNotifier direkt nötig!

  • Der WeatherState verwendet bereits das WeatherData-Objekt, das nun das greenlandTemperatureSum-Feld enthält.
  • Der WeatherNotifier ruft das WeatherRepository auf, das nun ein WeatherData-Objekt mit befüllter GTS zurückliefert. Der Notifier reicht dieses Objekt einfach an den State weiter.

Das ist ein weiterer Beleg für die Vorteile einer guten Entkopplung: Die UI und der State-Notifier müssen die Details der GTS-Berechnung nicht kennen.

Schritt 10: Ausführen und Testen!

Starte die App (F5):

  1. Lade das Wetter für einen Ort (GPS oder Suche).
  2. Unter der aktuellen Temperatur und vor dem Diagramm sollte nun eine neue Karte mit der Grünlandtemperatursumme erscheinen.
  3. Beobachte die Logs: Du solltest sehen, wie der GtsCalculatorService arbeitet, historische und ggf. Forecast-Daten für die Ergänzung abruft. Beim zweiten Laden für denselben Ort (innerhalb der Cache-Dauer) sollten die Daten schneller aus dem Cache kommen.
  4. Vergleich (optional): Wenn du Zugang zu offiziellen GTS-Werten für deinen Ort hast, kannst du versuchen, die Ergebnisse zu vergleichen. Beachte, dass es leichte Abweichungen geben kann, je nach genauer Datenquelle und Methodik der offiziellen Stellen.

Fazit und Abschluss der Kernserie

Herzlichen Glückwunsch! Du hast die Grünlandtemperatursumme erfolgreich in deine Wetter-App integriert. Damit haben wir alle Kernfunktionen umgesetzt, die wir uns am Anfang vorgenommen hatten.

In diesem Teil hast du gelernt:

  • Wie man Daten von einem anderen API-Endpunkt (Archiv-API) abruft.
  • Wie man historische Daten verarbeitet.
  • Wie man eine spezifische fachliche Berechnung (GTS mit Monatsfaktoren) implementiert.
  • Wie man einen einfachen In-Memory-Cache erstellt und nutzt, um API-Anfragen zu reduzieren.
  • Wie man Daten aus verschiedenen Quellen (historisch, Forecast) kombiniert, um ein vollständiges Bild zu erhalten.
  • Wie eine gut strukturierte App das Hinzufügen komplexer Logik erleichtert, ohne dass alle Teile der App angepasst werden müssen.

Unsere Wetter-App ist nun ein ziemlich umfassendes Projekt, das viele Aspekte der Flutter-Entwicklung abdeckt. Du hast eine solide Grundlage geschaffen, auf der du aufbauen und weitere Ideen umsetzen kannst!

Wie geht es weiter?

Das war das Ende der geplanten Kernfunktionen. Aber eine App ist selten „fertig“. Mögliche nächste Schritte könnten sein:

  • UI-Verfeinerungen: Icons für Wetterbedingungen, schönere Übergänge, Anpassung an verschiedene Bildschirmgrößen.
  • Weitere Wetterdaten: Windgeschwindigkeit, Luftfeuchtigkeit, Sonnenaufgang/-untergang.
  • Einstellungen: Wahl der Temperatureinheit (Celsius/Fahrenheit), Anpassung der Diagramm-Optik.
  • Caching verbessern: Persistenter Cache (z.B. mit shared_preferences oder einer lokalen Datenbank).
  • Testing vertiefen: Mehr Unit-, Widget- und Integration-Tests schreiben.
  • Fehler-Reporting: Integration eines Dienstes wie Sentry oder Firebase Crashlytics.
  • Deployment: Die App für den Google Play Store vorbereiten und veröffentlichen.

Ich hoffe, diese Serie hat dir Spaß gemacht und dir geholfen, Flutter und die App-Entwicklung besser zu verstehen. Nutze das Gelernte als Sprungbrett für deine eigenen Projekte!

Vielen Dank fürs Mitmachen und viel Erfolg bei deinen zukünftigen Flutter-Abenteuern!


Weiterführende Ressourcen & Vertiefung

  1. Open-Meteo Archiv API:
  2. Grünlandtemperatursumme (GTS):
    • Agrarmeteorologische Informationen: Suche nach „Grünlandtemperatursumme Erklärung“ oder „GTS Landwirtschaft“, um mehr über die Bedeutung und Berechnungsmethoden zu erfahren (z.B. von Wetterdiensten oder landwirtschaftlichen Portalen).
  3. Caching-Strategien in Flutter:
    • Einfaches In-Memory Caching: Wie wir es implementiert haben, ist ein guter Start.
    • shared_preferences: https://pub.dev/packages/shared_preferences – Für das Speichern einfacher Schlüssel-Wert-Paare (kleine Datenmengen, die auch nach App-Neustart erhalten bleiben).
    • Lokale Datenbanken (z.B. sqflite): https://pub.dev/packages/sqflite – Für komplexere, strukturierte Daten, die persistent gespeichert werden sollen.
  4. Parallele Ausführung mit Future:

Deine Flutter Wetter-App: Schritt für Schritt – Teil 5

Teil 5: Kurven zeichnen – Temperaturverläufe mit Diagrammen visualisieren

Hallo und herzlich willkommen zurück!

In Teil 4 haben wir unserer Wetter-App eine wichtige Funktion spendiert: die Adresssuche. Nun können wir das aktuelle Wetter nicht nur für unseren GPS-Standort, sondern für jeden beliebigen Ort abfragen. Das ist schon ziemlich cool!

Aber oft sagt ein Bild mehr als tausend Worte – oder in unserem Fall, eine einzelne Temperaturzahl. Wie hat sich die Temperatur entwickelt? Was erwartet uns in den nächsten Tagen? Um diese Fragen zu beantworten, werden wir heute ein Liniendiagramm in unsere App integrieren, das den Temperaturverlauf der letzten 7 Tage und eine Prognose für die nächsten 7 Tage anzeigt.

Das Ziel für heute:

  • Wir untersuchen den Code für Teil 5 aus unserem Repository.
  • Wir lernen das fl_chart-Paket kennen, ein mächtiges Werkzeug zur Erstellung von Diagrammen in Flutter.
  • Wir passen unsere Datenabfrage an, um auch stündliche Temperaturdaten von Open-Meteo zu erhalten.
  • Wir sehen, wie diese Daten in eine für das Diagramm geeignete Form gebracht werden.
  • Wir bauen ein neues Widget (TemperatureChart), das die Daten visualisiert, inklusive Achsenbeschriftung, geglätteter Linie und interaktiven Tooltips.

Warum Diagramme?

Diagramme sind ein hervorragendes Mittel, um Trends und Muster in Daten schnell erfassbar zu machen. Ein Blick auf die Temperaturkurve gibt uns ein viel besseres Gefühl für das Wetter als nur die aktuelle Zahl. Für unser Lernprojekt ist es außerdem eine tolle Gelegenheit, uns mit Datenvisualisierung in Flutter auseinanderzusetzen.

Schritt 1: Den Code für Teil 5 holen

Der Code für diesen Beitrag ist wie gewohnt im Git-Repository vorbereitet.

  1. Öffne ein Terminal in deinem Projektordner (flutter_weather_app_blog).
  2. Stelle sicher, dass du keine ungespeicherten Änderungen hast.
  3. Wechsle zum main-Branch und aktualisiere:
    git checkout main
    git pull origin main
  4. Checke den Code-Stand für Teil 5 aus: (Tag-Name ggf. anpassen)
    git checkout part5-temperature-chart
    (Denke an die ‚detached HEAD‘-Meldung.)
  5. Ganz wichtig: Abhängigkeiten holen & Code generieren:
    flutter pub get
    dart run build_runner build --delete-conflicting-outputs
    (Wir haben das fl_chart-Paket hinzugefügt und einige Datenmodelle erweitert.)

Öffne das Projekt nun in VS Code. Auf den ersten Blick sieht die App vielleicht noch nicht viel anders aus, aber unter der Haube hat sich einiges getan, um die Diagrammdarstellung vorzubereiten und zu implementieren.

Schritt 2: Die neue Werkzeugkiste – Das fl_chart-Paket

Um Diagramme zu zeichnen, ohne das Rad neu erfinden zu müssen, verwenden wir ein beliebtes Flutter-Paket namens fl_chart.

  • pubspec.yaml: In dieser Datei findest du unter dependencies: den neuen Eintrag:
    dependencies:
    # ... andere Pakete ...
    fl_chart: ^0.68.0 # Version prüfen

    fl_chart ist sehr vielseitig und unterstützt verschiedene Diagrammtypen (Linien-, Balken-, Kreisdiagramme etc.). Wir konzentrieren uns auf Liniendiagramme.

Schritt 3: Mehr Daten von der API – Stündliche Temperaturen

Unser Diagramm soll einen Verlauf über 14 Tage zeigen (7 Vergangenheit, 7 Zukunft). Dafür brauchen wir detailliertere Daten als nur die aktuelle Temperatur.

  • WeatherApiService (lib/src/features/weather/data/datasources/weather_api_service.dart):
    • Die Methode getCurrentWeather wurde umbenannt zu getForecastWeather, da sie jetzt mehr als nur die aktuellen Daten liefert.
    • Entscheidende Änderung in den queryParameters:
      // Ausschnitt aus getForecastWeather in WeatherApiService
      final queryParameters = {
      // ... latitude, longitude, current_weather ...
      'hourly': 'temperature_2m', // NEU: Fordert stündliche Temperaturdaten an
      'past_days': pastDays.toString(), // NEU: z.B. 7
      'forecast_days': forecastDays.toString(), // NEU: z.B. 7
      // ... timezone ...
      };

      Wir fordern jetzt explizit hourly=temperature_2m an und spezifizieren über past_days und forecast_days den gewünschten Zeitraum. Open-Meteo liefert uns dann eine lange Liste von Zeitstempeln und den dazugehörigen Temperaturen im 2-Meter-Höhenintervall.

Schritt 4: Die neuen Datenstrukturen (data Layer Models)

Die API liefert die stündlichen Daten in einer spezifischen Struktur. Wir brauchen neue Dart-Klassen (Models), um diese abzubilden:

  • lib/src/features/weather/data/models/hourly_units_model.dart (NEU):
    • Open-Meteo sendet ein separates Objekt hourly_units, das die Einheiten für die stündlichen Werte angibt (z.B. "time": "iso8601", "temperature_2m": "°C"). Diese kleine Klasse bildet das ab.
  • lib/src/features/weather/data/models/hourly_data_model.dart (NEU):
    • Das ist das Herzstück der neuen API-Daten. Es bildet das hourly-Objekt aus der API-Antwort ab, das typischerweise zwei Listen enthält:
      • time: Eine Liste von Zeitstempel-Strings (z.B. "2023-10-27T10:00").
      • temperature_2m: Eine Liste von Temperaturwerten (Zahlen).
    • Die fromJson-Methode in dieser Klasse ist wichtig: Sie nimmt die rohen Listen aus dem JSON, parst die Zeitstempel-Strings mit unserem DateFormatter.tryParseApiDateTime in DateTime-Objekte und wandelt die Temperaturwerte in double um. Sie stellt auch sicher, dass beide Listen die gleiche Länge haben und behandelt fehlerhafte oder fehlende Werte (z.B. indem sie double.nan für ungültige Temperaturen verwendet).
  • lib/src/features/weather/data/models/forecast_response_model.dart (GEÄNDERT):
    • Unser Haupt-Antwortmodell wurde erweitert, um die neuen HourlyUnitsModel und HourlyDataModel aufzunehmen:
      // Ausschnitt aus ForecastResponseModel
      class ForecastResponseModel {
      // ... bestehende Felder ...
      final CurrentWeatherModel? currentWeather;
      final HourlyUnitsModel? hourlyUnits; // NEU
      final HourlyDataModel? hourly; // NEU

      // Konstruktor und toJson angepasst...

      factory ForecastResponseModel.fromJson(Map json) {
      // ... bestehendes Parsing ...
      final units = json.containsKey('hourly_units') /* ... */ ? HourlyUnitsModel.fromJson(json['hourly_units']) : null;
      final data = json.containsKey('hourly') /* ... */ ? HourlyDataModel.fromJson(json['hourly']) : null;
      return ForecastResponseModel(
      // ...
      hourlyUnits: units,
      hourly: data,
      );
      }
      }

Schritt 5: Die App-internen Daten (domain Layer Entities)

Unsere App-Logik und UI sollten nicht direkt mit den API-spezifischen Models arbeiten. Wir brauchen eine saubere, app-interne Repräsentation der Daten.

  • lib/src/features/weather/domain/entities/chart_point.dart (NEU):
    • Eine sehr einfache Klasse, die einen einzelnen Punkt im Diagramm repräsentiert:
      class ChartPoint extends Equatable {
      final DateTime time; // X-Wert
      final double temperature; // Y-Wert
      // ... Konstruktor, props ...
      }

  • lib/src/features/weather/domain/entities/weather_data.dart (GEÄNDERT/ERSETZT):
    • Diese Entität, die bisher nur CurrentWeatherData hieß und nur die aktuelle Temperatur enthielt, wurde nun zur zentralen WeatherData-Klasse.
    • Sie enthält jetzt zusätzlich eine Liste von ChartPoint-Objekten für den Temperaturverlauf:
      // Ausschnitt aus WeatherData
      class WeatherData extends Equatable {
      final double currentTemperature;
      final DateTime? lastUpdatedTime;
      final List hourlyForecast; // NEU

      // Konstruktor, props, empty, copyWith angepasst...
      }

    • (Die alte current_weather_data.dart kann gelöscht werden, wenn sie nicht mehr referenziert wird.)

Schritt 6: Datenaufbereitung im Repository (data Layer)

Das WeatherRepositoryImpl (lib/src/features/weather/data/repositories/weather_repository_impl.dart) ist dafür zuständig, die Rohdaten vom WeatherApiService zu holen und sie in unsere App-internen WeatherData-Entität umzuwandeln.

  • getWeatherForLocation-Methode (GEÄNDERT):
    • Ruft jetzt _apiService.getForecastWeather() auf (die umbenannte Methode, die auch stündliche Daten holt).
    • Nachdem die aktuelle Temperatur extrahiert wurde, iteriert es durch die time– und temperature_2m-Listen aus dem forecastResponse.hourly-Objekt.
    • Für jedes gültige Zeit/Temperatur-Paar wird ein ChartPoint-Objekt erstellt und der hourlyPoints-Liste hinzugefügt. Ungültige Temperaturen (NaN) werden übersprungen.
    • Am Ende wird das WeatherData-Objekt mit currentTemperature, lastUpdatedTime und der hourlyForecast-Liste erstellt und zurückgegeben.
    • Das Interface WeatherRepository (domain/repositories/weather_repository.dart) wurde natürlich angepasst, sodass getWeatherForLocation jetzt Future&lt;Either> zurückgibt.

Schritt 7: State Management (application Layer)

Die Änderungen im WeatherState und WeatherNotifier sind minimal, da unsere Architektur gut vorbereitet war.

  • lib/src/features/weather/application/weather_state.dart (GEÄNDERT):
    • Das Feld currentWeatherData wurde durch weatherData (vom Typ WeatherData, unserer neuen, umfassenderen Entität) ersetzt.
    • Die initial() und copyWith() Methoden wurden entsprechend angepasst.
  • lib/src/features/weather/application/weather_notifier.dart (MINIMAL GEÄNDERT):
    • Die Methode _fetchWeatherDataAndUpdateState nimmt nun WeatherData vom Repository entgegen und speichert es im weatherData-Feld des WeatherState. Die Logik an sich bleibt gleich, da das Repository die Hauptarbeit der Datenumwandlung übernimmt.

Schritt 8: Das Diagramm-Widget (presentation Layer)

Jetzt kommt der spannende Teil – die Visualisierung!

  • lib/src/features/weather/presentation/widgets/temperature_chart.dart (NEU):

    • Dies ist ein neues StatelessWidget, das die chartData (eine List) als Parameter erwartet.
    • Kernstück: LineChart von fl_chart:
      • LineChartData: Konfiguriert das gesamte Diagramm.
      • lineBarsData: Definiert die Linien. Wir haben eine LineChartBarData.
        • spots: Hier werden unsere ChartPoint-Objekte in FlSpot-Objekte umgewandelt, die fl_chart versteht (FlSpot(zeit_als_double, temperatur)).
        • isCurved: true: Macht die Linie schön weich.
        • color, barWidth: Aussehen der Linie.
        • dotData: FlDotData(show: false): Wir zeigen keine einzelnen Punkte auf der Linie an.
        • belowBarData: Füllt den Bereich unter der Linie mit einem Farbverlauf (optional, aber schick).
      • titlesData: Konfiguriert die Achsenbeschriftungen.
        • bottomTitles: Für die X-Achse (Zeit). getTitlesWidget ist eine Funktion, die für jeden Achsenpunkt ein Widget zurückgibt. Wir formatieren hier den Zeitstempel mit unserem DateFormatter.formatChartAxisDay (z.B. „Mo 15.07.“) und zeigen nur alle paar Tage ein Label, um Überlappung zu vermeiden. Die SideTitleWidget(meta: meta, ...) Konstruktion ist hier wichtig.
        • leftTitles: Für die Y-Achse (Temperatur). Zeigt Temperaturwerte in sinnvollen Intervallen (z.B. alle 5 Grad).
      • gridData: Zeichnet das Hintergrundgitter.
      • borderData: Zeichnet einen Rahmen um das Diagramm.
      • lineTouchData: Ermöglicht Interaktion.
        • touchTooltipData: Konfiguriert die Tooltips, die erscheinen, wenn man auf die Linie tippt. getTooltipItems erstellt den Inhalt des Tooltips (Datum, Uhrzeit, Temperatur). getTooltipColor (früher tooltipBgColor) setzt die Hintergrundfarbe des Tooltips.
      • minY, maxY: Bestimmen den sichtbaren Bereich der Y-Achse. Wir berechnen diese dynamisch aus den Daten und fügen etwas Puffer hinzu.
    • Deutsche Tageskürzel: In lib/main.dart haben wir initializeDateFormatting('de_DE', null); hinzugefügt. Dadurch verwendet DateFormat im DateFormatter standardmäßig deutsche Formate, also auch „Mo, Di, Mi…“ für die Tageskürzel in der X-Achsen-Beschriftung.
  • lib/src/features/weather/presentation/screens/weather_screen.dart (GEÄNDERT):

    • _buildSuccessContent (NEUE Hilfsmethode): Um den _buildContent-Switch übersichtlich zu halten, wurde der Code, der bei WeatherStatus.success angezeigt wird, in diese neue Methode ausgelagert.
    • Innerhalb von _buildSuccessContent wird nun zusätzlich zum CurrentTemperatureDisplay auch das TemperatureChart-Widget hinzugefügt. Es bekommt data.hourlyForecast (wobei data hier das WeatherData-Objekt aus dem State ist) übergeben.
    • Die gesamte Erfolgsansicht ist in eine ListView gewickelt, damit der Inhalt scrollbar wird, falls er nicht auf den Bildschirm passt (was mit dem Diagramm nun der Fall sein kann).

Schritt 9: Ausführen und Bestaunen!

Starte die App (F5):

  1. Lasse das Wetter für deinen aktuellen Standort oder einen gesuchten Ort laden.
  2. Unter der aktuellen Temperatur solltest du nun ein Liniendiagramm sehen!
  3. Interaktion: Tippe auf verschiedene Punkte der Linie. Ein Tooltip sollte mit dem genauen Datum, der Uhrzeit und der Temperatur für diesen Punkt erscheinen.
  4. Verlauf: Die Linie sollte die Temperatur der letzten 7 Tage (Vergangenheit) und der nächsten 7 Tage (Prognose) darstellen.
  5. Achsen: Die X-Achse sollte Tage anzeigen (z.B. „Heute“, „Morgen“, „Mi 17.07.“), die Y-Achse die Temperaturskala.

Was haben wir gelernt?

  • Wie man ein externes Paket (fl_chart) für komplexe UI-Elemente einbindet.
  • Wie man API-Anfragen anpasst, um mehr Daten (stündliche Werte) zu erhalten.
  • Wie man Datenmodelle und Entitäten erweitert, um neue Informationen zu speichern.
  • Wie man Rohdaten von einer API für die Darstellung in einem Diagramm aufbereitet (Mapping zu ChartPoint und FlSpot).
  • Die Grundlagen der Konfiguration eines LineChart mit fl_chart, inklusive:
    • Datenpunkte (spots)
    • Aussehen der Linie (LineChartBarData)
    • Achsenbeschriftung (titlesData, SideTitleWidget)
    • Gitternetz (gridData)
    • Interaktive Tooltips (lineTouchData)
  • Wie man die UI strukturert (ListView), um auch größere Inhalte scrollbar zu machen.
  • Die Wichtigkeit der deutschen Locale-Initialisierung für korrekte Datumsformate.

Ausblick auf Teil 6:

Unsere App wird immer funktionaler und sieht auch noch besser aus! Im nächsten und vorerst letzten Teil dieser Kernserie implementieren wir unsere Spezialfunktion: die Berechnung und Anzeige der Grünlandtemperatursumme (GTS). Dafür müssen wir historische Tagesmittelwerte von einer anderen Open-Meteo API abrufen und eine spezifische Berechnungslogik umsetzen.

Das wird noch einmal spannend und zeigt, wie man mit Flutter auch komplexere fachliche Anforderungen umsetzen kann. Bleib dran!


Weiterführende Ressourcen & Vertiefung

In diesem Teil haben wir einige neue Konzepte und Werkzeuge kennengelernt. Hier sind Links, falls du tiefer eintauchen möchtest:

  1. fl_chart Paket:
  2. Datenmodellierung & JSON Parsing in Dart:
  3. Datum & Zeit Formatierung (intl Paket):
  4. Flutter State Management (Riverpod):
    • Offizielle Riverpod Dokumentation: https://riverpod.dev/ – Die beste Quelle, um Riverpod von Grund auf zu lernen oder spezifische Konzepte wie StateNotifierProvider, ref.watch und ref.read nachzuschlagen.
  5. Flutter Layout Grundlagen (ListView, Column, SizedBox):

© 2025 Allround-Blog

Theme von Anders NorénHoch ↑