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.
- Öffne ein Terminal in deinem Projektordner (
flutter_weather_app_blog
). - Stelle sicher, dass du keine ungespeicherten Änderungen hast.
- Wechsle zum
main
-Branch und aktualisiere:git checkout main
git pull origin main - Checke den Code-Stand für Teil 5 aus: (Tag-Name ggf. anpassen)
(Denke an die ‚detached HEAD‘-Meldung.)git checkout part5-temperature-chart
- Ganz wichtig: Abhängigkeiten holen & Code generieren:
(Wir haben dasflutter pub get
dart run build_runner build --delete-conflicting-outputsfl_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 unterdependencies:
den neuen Eintrag:dependencies:
# ... andere Pakete ...
fl_chart: ^0.68.0 # Version prüfenfl_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 zugetForecastWeather
, 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 explizithourly=temperature_2m
an und spezifizieren überpast_days
undforecast_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.
- Die Methode
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.
- Open-Meteo sendet ein separates Objekt
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 unseremDateFormatter.tryParseApiDateTime
inDateTime
-Objekte und wandelt die Temperaturwerte indouble
um. Sie stellt auch sicher, dass beide Listen die gleiche Länge haben und behandelt fehlerhafte oder fehlende Werte (z.B. indem siedouble.nan
für ungültige Temperaturen verwendet).
- Das ist das Herzstück der neuen API-Daten. Es bildet das
lib/src/features/weather/data/models/forecast_response_model.dart
(GEÄNDERT):- Unser Haupt-Antwortmodell wurde erweitert, um die neuen
HourlyUnitsModel
undHourlyDataModel
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,
);
}
}
- Unser Haupt-Antwortmodell wurde erweitert, um die neuen
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 ...
}
- Eine sehr einfache Klasse, die einen einzelnen Punkt im Diagramm repräsentiert:
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 zentralenWeatherData
-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.)
- Diese Entität, die bisher nur
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
– undtemperature_2m
-Listen aus demforecastResponse.hourly
-Objekt. - Für jedes gültige Zeit/Temperatur-Paar wird ein
ChartPoint
-Objekt erstellt und derhourlyPoints
-Liste hinzugefügt. Ungültige Temperaturen (NaN
) werden übersprungen. - Am Ende wird das
WeatherData
-Objekt mitcurrentTemperature
,lastUpdatedTime
und derhourlyForecast
-Liste erstellt und zurückgegeben. - Das Interface
WeatherRepository
(domain/repositories/weather_repository.dart
) wurde natürlich angepasst, sodassgetWeatherForLocation
jetztFuture<Either>
zurückgibt.
- Ruft jetzt
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 durchweatherData
(vom TypWeatherData
, unserer neuen, umfassenderen Entität) ersetzt. - Die
initial()
undcopyWith()
Methoden wurden entsprechend angepasst.
- Das Feld
lib/src/features/weather/application/weather_notifier.dart
(MINIMAL GEÄNDERT):- Die Methode
_fetchWeatherDataAndUpdateState
nimmt nunWeatherData
vom Repository entgegen und speichert es imweatherData
-Feld desWeatherState
. Die Logik an sich bleibt gleich, da das Repository die Hauptarbeit der Datenumwandlung übernimmt.
- Die Methode
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 diechartData
(eineList
) als Parameter erwartet. - Kernstück:
LineChart
vonfl_chart
:LineChartData
: Konfiguriert das gesamte Diagramm.lineBarsData
: Definiert die Linien. Wir haben eineLineChartBarData
.spots
: Hier werden unsereChartPoint
-Objekte inFlSpot
-Objekte umgewandelt, diefl_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 unseremDateFormatter.formatChartAxisDay
(z.B. „Mo 15.07.“) und zeigen nur alle paar Tage ein Label, um Überlappung zu vermeiden. DieSideTitleWidget(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ühertooltipBgColor
) 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 wirinitializeDateFormatting('de_DE', null);
hinzugefügt. Dadurch verwendetDateFormat
imDateFormatter
standardmäßig deutsche Formate, also auch „Mo, Di, Mi…“ für die Tageskürzel in der X-Achsen-Beschriftung.
- Dies ist ein neues
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 beiWeatherStatus.success
angezeigt wird, in diese neue Methode ausgelagert.- Innerhalb von
_buildSuccessContent
wird nun zusätzlich zumCurrentTemperatureDisplay
auch dasTemperatureChart
-Widget hinzugefügt. Es bekommtdata.hourlyForecast
(wobeidata
hier dasWeatherData
-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
):
- Lasse das Wetter für deinen aktuellen Standort oder einen gesuchten Ort laden.
- Unter der aktuellen Temperatur solltest du nun ein Liniendiagramm sehen!
- Interaktion: Tippe auf verschiedene Punkte der Linie. Ein Tooltip sollte mit dem genauen Datum, der Uhrzeit und der Temperatur für diesen Punkt erscheinen.
- Verlauf: Die Linie sollte die Temperatur der letzten 7 Tage (Vergangenheit) und der nächsten 7 Tage (Prognose) darstellen.
- 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
undFlSpot
). - Die Grundlagen der Konfiguration eines
LineChart
mitfl_chart
, inklusive:- Datenpunkte (
spots
) - Aussehen der Linie (
LineChartBarData
) - Achsenbeschriftung (
titlesData
,SideTitleWidget
) - Gitternetz (
gridData
) - Interaktive Tooltips (
lineTouchData
)
- Datenpunkte (
- 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:
fl_chart
Paket:- Offizielle Dokumentation & Beispiele: https://flchart.dev/ – Die beste Anlaufstelle für alle Details zu
fl_chart
, inklusive einer tollen Beispiel-Galerie für verschiedene Diagrammtypen und Konfigurationen. - Pub.dev Seite: https://pub.dev/packages/fl_chart – Für Installationsanweisungen, Versionshinweise und den Link zur API-Dokumentation.
- LineChart Beispiele (spezifisch): https://flchart.dev/docs/chart_types/line_chart – Beispiele und Erklärungen speziell für Liniendiagramme.
- Offizielle Dokumentation & Beispiele: https://flchart.dev/ – Die beste Anlaufstelle für alle Details zu
- Datenmodellierung & JSON Parsing in Dart:
- Dart Sprach-Tour (JSON): https://dart.dev/guides/language/language-tour#json – Grundlagen zum Umgang mit JSON in Dart.
- Flutter Dokumentation (JSON und Serialisierung): https://docs.flutter.dev/data-and-backend/json – Umfassender Guide, der auch automatische Code-Generierung für JSON anspricht (was wir hier manuell gemacht haben).
- Datum & Zeit Formatierung (
intl
Paket):intl
Paket auf Pub.dev: https://pub.dev/packages/intl – Enthält die Dokumentation zurDateFormat
-Klasse.- Flutter Internationalization Guide: https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization – Erklärt das Konzept der Lokalisierung (Locales), was wichtig ist, um z.B. deutsche Tageskürzel zu bekommen (
initializeDateFormatting
).
- 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
undref.read
nachzuschlagen.
- Offizielle Riverpod Dokumentation: https://riverpod.dev/ – Die beste Quelle, um Riverpod von Grund auf zu lernen oder spezifische Konzepte wie
- Flutter Layout Grundlagen (ListView, Column, SizedBox):
- Flutter Layout Dokumentation: https://docs.flutter.dev/ui/layout/tutorial – Eine gute Einführung, wie man Widgets anordnet.
- ListView Klasse: https://api.flutter.dev/flutter/widgets/ListView-class.html – Dokumentation zur
ListView
, die wir verwendet haben, um den Inhalt scrollbar zu machen.
0 Kommentare
1 Pingback