Skip to content

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ügen
bash
flutter pub get

9.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 → Fehler

Automatisches 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.0

Schritt 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 build

Schritt 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)

  1. Besuchen Sie https://app.quicktype.io/
  2. Fügen Sie Ihr JSON-Beispiel ein
  3. Wählen Sie "Dart" als Zielsprache
  4. 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 URL

Lösung:

dart
final response = await dio.get('https://example.com');  // RICHTIG

Fehler 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:

  1. Überprüfen Sie die JSON-Struktur der API-Antwort
  2. Stellen Sie sicher, dass Ihre Dart-Klasse mit dem JSON übereinstimmt
  3. 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:

  1. Erstellen Sie eine App, die Benutzerdaten von einer API abruft und in einer Liste anzeigt
  2. Implementieren Sie eine Suchfunktion, die API-Requests mit Suchparametern sendet
  3. Erstellen Sie ein Formular, das Daten per POST-Request an einen Server sendet
  4. Implementieren Sie Fehlerbehandlung und zeigen Sie entsprechende Fehlermeldungen an
  5. Verwenden Sie Dio-Interceptor, um Authentifizierungs-Token zu allen Requests hinzuzufügen

Häufige Fehler:

  • ❌ Internet-Berechtigung in AndroidManifest.xml vergessen
  • ❌ 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

Frei für alle Anfänger