Appearance
Kapitel 9: Netzwerkrequests
9.1 Konzept der Netzwerkrequests
Warum Netzwerkrequests?
Mobile Apps mute oft Daten vom Internet abrufen:
- ✅ Benutzerdaten abrufen (Profile, Einstellungen)
- ✅ Neuigkeiten laden ( News-Feed)
- ✅ Bilder/Videos herunterladen
- ✅ Daten an Server senden (Login, Registrierung)
Grundlegender Ablauf
Flutter-App → Netzwerkrequest → Server (Backend)
↑ |
|___________________________________________|
Antwort (JSON, XML, etc.)9.2 Netzwerk-Plugins (empfohlen: Dio)
Warum Dio?
Vorteile gegenüber http (Standard-Bibliothek):
- ✅ Unterstützung von Interceptors (Interceptor)
- ✅ Request/Response-Abbruch
- ✅ FormData (Datei-Upload)
- ✅ Automatische JSON-Konvertierung (mit Zusatzpaketen)
- ✅ Besseres Fehlerhandling
Dio installieren
pubspec.yaml:
yaml
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0 # Dio-Paket hinzufügenbash
flutter pub get9.3 GET-Request (Daten abrufen)
Einfacher GET-Request
dart
import 'package:dio/dio.dart';
// Dio-Instanz erstellen
final dio = Dio();
Future<void> ladeDaten() async {
try {
// GET-Request senden
final response = await dio.get('https://jsonplaceholder.typicode.com/posts/1');
// Antwort verarbeiten
print('Statuscode: ${response.statusCode}');
print('Daten: ${response.data}');
} catch (e) {
// Fehler abfangen
print('Fehler: $e');
}
}GET-Request mit Parametern
dart
Future<void> ladeBenutzer() async {
try {
final response = await dio.get(
'https://jsonplaceholder.typicode.com/posts',
queryParameters: {
'_limit': 5, // Nur 5 Ergebnisse
},
);
print('Erhaltene Daten: ${response.data}');
} catch (e) {
print('Fehler: $e');
}
}Antwort parsen (JSON → Dart-Objekt)
Manuelle Parsing:
dart
// Angenommen, API gibt JSON zurück:
// {
// "id": 1,
// "title": "BeispielTitel",
// "body": "BeispielInhalt"
// }
final response = await dio.get('https://jsonplaceholder.typicode.com/posts/1');
// Manuelle Parsing
final data = response.data;
final titel = data['title'];
final inhalt = data['body'];
print('Titel: $titel');
print('Inhalt: $inhalt');Bessere Lösung: JSON zu Dart-Klasse konvertieren (siehe Abschnitt 9.5)
9.4 POST-Request (Daten senden)
Einfacher POST-Request
dart
Future<void> sendeDaten() async {
try {
final response = await dio.post(
'https://jsonplaceholder.typicode.com/posts',
data: {
'title': 'Mein Titel',
'body': 'Mein Inhalt',
'userId': 1,
},
);
print('Erfolg! Statuscode: ${response.statusCode}');
print('Antwort: ${response.data}');
} catch (e) {
print('Fehler: $e');
}
}POST-Request mit FormData (Datei-Upload)
dart
Future<void> ladeBildHoch() async {
try {
final formData = FormData.fromMap({
'name': 'mein_bild.jpg',
'file': await MultipartFile.fromFile(
'/pfad/zu/bild.jpg',
filename: 'bild.jpg',
),
});
final response = await dio.post(
'https://example.com/upload',
data: formData,
);
print('Hochladen erfolgreich!');
} catch (e) {
print('Fehler beim Hochladen: $e');
}
}9.5 Antwort parsen (JSON → Dart-Objekt)
Manuelles Parsing (nicht empfohlen)
dart
final response = await dio.get('https://...');
final data = response.data;
// Fehleranfällig!
final titel = data['title']; // FALSCHER Schlüssel → FehlerAutomatisches Parsing mit json_serializable (empfohlen)
Schritt 1: Abhängigkeiten installieren
yaml
dependencies:
json_annotation: ^4.8.0
dev_dependencies:
build_runner: ^2.4.0
json_serializable: ^6.7.0Schritt 2: Dart-Klasse definieren
dart
import 'package:json_annotation/json_annotation.dart';
part 'post_model.g.dart'; // WICHTIG: Teil-Datei
@JsonSerializable()
class Post {
final int id;
final String title;
final String body;
final int userId;
Post({
required this.id,
required this.title,
required this.body,
required this.userId,
});
// Von JSON erstellen
factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
// Zu JSON konvertieren
Map<String, dynamic> toJson() => _$PostToJson(this);
}Schritt 3: Code generieren
bash
flutter pub run build_runner buildSchritt 4: Verwenden
dart
Future<void> ladePost() async {
try {
final response = await dio.get('https://jsonplaceholder.typicode.com/posts/1');
// JSON → Dart-Objekt
final post = Post.fromJson(response.data);
print('Titel: ${post.title}');
print('Inhalt: ${post.body}');
} catch (e) {
print('Fehler: $e');
}
}Schnellere Lösung: QuickType (Online-Tool)
- Besuchen Sie https://app.quicktype.io/
- Fügen Sie Ihr JSON-Beispiel ein
- Wählen Sie "Dart" als Zielsprache
- Kopieren Sie den generierten Dart-Code
9.6 Netzwerkrequest-Fehlerbehandlung
Häufige Fehlerarten
dart
Future<void> ladeDaten() async {
try {
final response = await dio.get('https://example.com/api');
// Erfolg
} on DioException catch (e) {
// Dio-spezifischer Fehler
if (e.type == DioExceptionType.connectionTimeout) {
print('Verbindungs-Timeout');
} else if (e.type == DioExceptionType.receiveTimeout) {
print('Empfangs-Timeout');
} else if (e.response != null) {
// Server hat geantwortet, aber mit Fehler-Statuscode
print('Server-Fehler: ${e.response?.statusCode}');
print('Fehlermeldung: ${e.response?.data}');
} else {
// Anderer Fehler (Netzwerkprobleme, etc.)
print('Netzwerkfehler: $e');
}
} catch (e) {
// Andere Fehler
print('Allgemeiner Fehler: $e');
}
}Globaler Fehler-Interceptor (Dio Interceptor)
dart
final dio = Dio();
void konfiguriereDio() {
// Request-Interceptor (vor Request)
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
print('Request gesendet: ${options.path}');
// Token hinzufügen (Authorization)
options.headers['Authorization'] = 'Bearer $token';
return handler.next(options);
},
onResponse: (response, handler) {
print('Antwort erhalten: ${response.statusCode}');
return handler.next(response);
},
onError: (DioException e, handler) {
print('Fehler aufgetreten: $e');
// Globales Fehlerhandling
return handler.next(e);
},
));
}9.7 Praxisbeispiel: Daten abrufen und anzeigen
Schritt 1: Modell-Klasse erstellen
dart
// post_model.dart
class Post {
final int id;
final String title;
final String body;
Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}Schritt 2: Service-Klasse für API-Requests
dart
// api_service.dart
import 'package:dio/dio.dart';
import 'post_model.dart';
class ApiService {
final dio = Dio();
Future<List<Post>> ladePosts() async {
try {
final response = await dio.get('https://jsonplaceholder.typicode.com/posts?_limit=10');
// JSON-Array → Liste von Post-Objekten
final List<dynamic> data = response.data;
return data.map((json) => Post.fromJson(json)).toList();
} catch (e) {
throw Exception('Fehler beim Laden der Posts: $e');
}
}
}Schritt 3: In Flutter-UI verwenden
dart
// posts_page.dart
import 'package:flutter/material.dart';
import 'api_service.dart';
import 'post_model.dart';
class PostsPage extends StatefulWidget {
const PostsPage({super.key});
@override
State<PostsPage> createState() => _PostsPageState();
}
class _PostsPageState extends State<PostsPage> {
final ApiService _apiService = ApiService();
List<Post> _posts = [];
bool _isLoading = false;
String _errorMessage = '';
@override
void initState() {
super.initState();
_ladePosts();
}
Future<void> _ladePosts() async {
setState(() {
_isLoading = true;
_errorMessage = '';
});
try {
final posts = await _apiService.ladePosts();
setState(() {
_posts = posts;
_isLoading = false;
});
} catch (e) {
setState(() {
_errorMessage = e.toString();
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Posts')),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: _ladePosts,
child: const Icon(Icons.refresh),
),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_errorMessage.isNotEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.error, size: 64, color: Colors.red),
const SizedBox(height: 16),
Text(_errorMessage, textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _ladePosts,
child: const Text('Erneut versuchen'),
),
],
),
);
}
return ListView.builder(
itemCount: _posts.length,
itemBuilder: (context, index) {
final post = _posts[index];
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
title: Text(post.title),
subtitle: Text(
post.body,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
);
},
);
}
}9.8 Häufige Fehler
Fehler 1: Netzwerk-Request-Adresse falsch
Fehler:
dart
final response = await dio.get('htt://example.com'); // FALSCHE URLLösung:
dart
final response = await dio.get('https://example.com'); // RICHTIGFehler 2: Parameterformat falsch
Fehler:
dart
// FALSCH: Parameter direkt an URL anhängen
dio.get('https://example.com/api?limit=5');Richtig:
dart
// RICHTIG: queryParameters verwenden
dio.get('https://example.com/api', queryParameters: {'limit': 5});Fehler 3: JSON-Parsing fehlgeschlagen
Fehler:
dart
// FALSCH: JSON-Struktur stimmt nicht mit Dart-Klasse überein
final post = Post.fromJson(response.data); // Fehler!Lösung:
- Überprüfen Sie die JSON-Struktur der API-Antwort
- Stellen Sie sicher, dass Ihre Dart-Klasse mit dem JSON übereinstimmt
- Verwenden Sie QuickType oder json_serializable
Fehler 4: Internet-Berechtigung fehlt (Android)
Fehler: App kann keine Netzwerkanfragen senden.
Lösung: android/app/src/main/AndroidManifest.xml bearbeiten:
xml
<manifest xmlns:android="...">
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Weitere Konfiguration -->
</manifest>Zusammenfassung
In diesem Kapitel haben Sie:
- ✅ Das Konzept der Netzwerkrequests verstanden
- ✅ Dio-Plugin für leistungsfähige Netzwerkanfragen installiert
- ✅ GET-Requests (Daten abrufen) implementiert
- ✅ POST-Requests (Daten senden) implementiert
- ✅ JSON-Antworten in Dart-Objekte konvertiert
- ✅ Fehlerbehandlung für Netzwerkanfragen implementiert
- ✅ Ein Praxisbeispiel (Daten abrufen und anzeigen) erstellt
- ✅ Häufige Fehler behoben
Nächstes Kapitel: Wir werden lokale Speicherung lernen (shared_preferences, sqflite).
Übungsaufgaben:
- Erstellen Sie eine App, die Benutzerdaten von einer API abruft und in einer Liste anzeigt
- Implementieren Sie eine Suchfunktion, die API-Requests mit Suchparametern sendet
- Erstellen Sie ein Formular, das Daten per POST-Request an einen Server sendet
- Implementieren Sie Fehlerbehandlung und zeigen Sie entsprechende Fehlermeldungen an
- Verwenden Sie Dio-Interceptor, um Authentifizierungs-Token zu allen Requests hinzuzufügen
Häufige Fehler:
- ❌ Internet-Berechtigung in
AndroidManifest.xmlvergessen - ❌ JSON-Struktur stimmt nicht mit Dart-Modellasse überein
- ❌ Fehlerbehandlung vergessen → App stürzt bei Netzwerkfehlern ab
- ❌
setState()nach asynchronen Operationen vergessen → UI aktualisiert sich nicht
