## Learning Objectives

By the end of this chapter, you will be able to:

- Make HTTP requests (GET, POST, PUT, DELETE) using the http package
- Parse JSON responses and convert them to Dart models
- Handle network errors, timeouts, and loading states
- Implement REST API client patterns and service layers
- Add request headers, authentication tokens, and query parameters
- Use async/await effectively for network operations

1

## Introduction to Networking in Flutter

Most mobile apps need to communicate with backend servers to fetch data, submit forms, and sync state. Flutter provides excellent support for HTTP networking through packages like `http` and `dio`. This chapter focuses on the standard `http` package for making REST API calls.

Key concept: All network operations in Flutter are asynchronous. Use `async`/`await` or `Future` chains to handle network responses without blocking the UI thread.

2

## Setting Up the HTTP Package

### Add Dependency

Add the `http` package to your `pubspec.yaml`:

### pubspec.yaml

    dependencies:
      flutter:
        sdk: flutter
      http: ^1.1.0

### Import in Dart File

    import 'package:http/http.dart' as http;
    import 'dart:convert';

3

## Making HTTP Requests

### GET Request

Fetch data from a server:

### Basic GET Request

    Future fetchData() async {
      final url = Uri.parse('https://api.example.com/users');
      final response = await http.get(url);

      if (response.statusCode == 200) {
        // Success
        final data = jsonDecode(response.body);
        print(data);
      } else {
        // Error
        print('Error: ${response.statusCode}');
      }
    }

### POST Request

Send data to a server:

### POST Request with JSON Body

    Future createUser(String name, String email) async {
      final url = Uri.parse('https://api.example.com/users');
      final response = await http.post(
        url,
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({
          'name': name,
          'email': email,
        }),
      );

      if (response.statusCode == 201) {
        final newUser = jsonDecode(response.body);
        print('Created: $newUser');
      }
    }

### PUT and DELETE Requests

Update and delete resources:

### PUT and DELETE Examples

    // Update user
    Future updateUser(String userId, Map data) async {
      final url = Uri.parse('https://api.example.com/users/$userId');
      final response = await http.put(
        url,
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(data),
      );
    }

    // Delete user
    Future deleteUser(String userId) async {
      final url = Uri.parse('https://api.example.com/users/$userId');
      final response = await http.delete(url);

      if (response.statusCode == 204) {
        print('User deleted');
      }
    }

4

## Adding Headers and Authentication

### Request Headers

Add custom headers for authentication, content type, and API keys:

### Headers Example

    Future fetchWithAuth(String token) async {
      final url = Uri.parse('https://api.example.com/protected');
      final response = await http.get(
        url,
        headers: {
          'Authorization': 'Bearer $token',
          'Content-Type': 'application/json',
          'X-API-Key': 'your-api-key',
        },
      );
    }

### Query Parameters

Add query parameters to URLs:

### Query Parameters

    Future searchUsers(String query, int page) async {
      final url = Uri.parse('https://api.example.com/users').replace(
        queryParameters: {
          'q': query,
          'page': page.toString(),
          'limit': '20',
        },
      );
      final response = await http.get(url);
    }

5

## Parsing JSON Responses

### Basic JSON Parsing

Convert JSON strings to Dart objects:

### Simple JSON Parsing

    // JSON string
    final jsonString = '{"name": "John", "age": 30}';

    // Parse to Map
    final Map json = jsonDecode(jsonString);
    print(json['name']); // John
    print(json['age']);  // 30

    // Convert back to JSON
    final String jsonOutput = jsonEncode(json);

### Creating Model Classes

Create Dart classes to represent API data:

### User Model Example

    class User {
      final String id;
      final String name;
      final String email;

      User({
        required this.id,
        required this.name,
        required this.email,
      });

      // Factory constructor from JSON
      factory User.fromJson(Map json) {
        return User(
          id: json['id'] as String,
          name: json['name'] as String,
          email: json['email'] as String,
        );
      }

      // Convert to JSON
      Map toJson() {
        return {
          'id': id,
          'name': name,
          'email': email,
        };
      }
    }

    // Usage
    Future> fetchUsers() async {
      final response = await http.get(Uri.parse('https://api.example.com/users'));
      if (response.statusCode == 200) {
        final List jsonList = jsonDecode(response.body);
        return jsonList.map((json) => User.fromJson(json)).toList();
      } else {
        throw Exception('Failed to load users');
      }
    }

6

## Error Handling

### Network Error Handling

Handle various types of network errors:

### Comprehensive Error Handling

    Future> fetchUsersSafe() async {
      try {
        final response = await http.get(
          Uri.parse('https://api.example.com/users'),
        ).timeout(Duration(seconds: 10));

        if (response.statusCode == 200) {
          final List jsonList = jsonDecode(response.body);
          return jsonList.map((json) => User.fromJson(json)).toList();
        } else if (response.statusCode == 401) {
          throw Exception('Unauthorized - please login');
        } else if (response.statusCode == 404) {
          throw Exception('Resource not found');
        } else {
          throw Exception('Server error: ${response.statusCode}');
        }
      } on SocketException {
        throw Exception('No internet connection');
      } on TimeoutException {
        throw Exception('Request timeout - please try again');
      } on FormatException {
        throw Exception('Invalid response format');
      } catch (e) {
        throw Exception('Unexpected error: $e');
      }
    }

### Required Imports

    import 'dart:io'; // for SocketException
    import 'dart:async'; // for TimeoutException

7

## Building a Service Layer

### API Service Pattern

Create a dedicated service class to centralize API calls:

### ApiService Example

    class ApiService {
      static const String baseUrl = 'https://api.example.com';
      String? _authToken;

      void setAuthToken(String token) {
        _authToken = token;
      }

      Map get _headers => {
        'Content-Type': 'application/json',
        if (_authToken != null) 'Authorization': 'Bearer $_authToken',
      };

      Future> getUsers() async {
        final response = await http.get(
          Uri.parse('$baseUrl/users'),
          headers: _headers,
        );

        if (response.statusCode == 200) {
          final List jsonList = jsonDecode(response.body);
          return jsonList.map((json) => User.fromJson(json)).toList();
        }
        throw _handleError(response);
      }

      Future createUser(User user) async {
        final response = await http.post(
          Uri.parse('$baseUrl/users'),
          headers: _headers,
          body: jsonEncode(user.toJson()),
        );

        if (response.statusCode == 201) {
          return User.fromJson(jsonDecode(response.body));
        }
        throw _handleError(response);
      }

      Exception _handleError(http.Response response) {
        switch (response.statusCode) {
          case 400:
            return Exception('Bad request');
          case 401:
            return Exception('Unauthorized');
          case 404:
            return Exception('Not found');
          case 500:
            return Exception('Server error');
          default:
            return Exception('Error: ${response.statusCode}');
        }
      }
    }

8

## Loading States and UI Integration

### Managing Loading States

Use StatefulWidget to manage loading, error, and data states:

### Loading State Pattern

    class UsersScreen extends StatefulWidget {
      @override
      _UsersScreenState createState() => _UsersScreenState();
    }

    class _UsersScreenState extends State {
      List _users = [];
      bool _isLoading = false;
      String? _error;
      final _apiService = ApiService();

      @override
      void initState() {
        super.initState();
        _loadUsers();
      }

      Future _loadUsers() async {
        setState(() {
          _isLoading = true;
          _error = null;
        });

        try {
          final users = await _apiService.getUsers();
          setState(() {
            _users = users;
            _isLoading = false;
          });
        } catch (e) {
          setState(() {
            _error = e.toString();
            _isLoading = false;
          });
        }
      }

      @override
      Widget build(BuildContext context) {
        if (_isLoading) {
          return Center(child: CircularProgressIndicator());
        }

        if (_error != null) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Error: $_error'),
                ElevatedButton(
                  onPressed: _loadUsers,
                  child: Text('Retry'),
                ),
              ],
            ),
          );
        }

        return ListView.builder(
          itemCount: _users.length,
          itemBuilder: (context, index) {
            final user = _users[index];
            return ListTile(
              title: Text(user.name),
              subtitle: Text(user.email),
            );
          },
        );
      }
    }

9

## Best Practices

### Networking Best Practices

- Always handle errors and provide user-friendly error messages.
- Add timeouts to prevent indefinite waiting.
- Use try-catch blocks around network calls.
- Centralize API calls in a service layer for reusability.
- Validate JSON structure before parsing to avoid crashes.
- Store base URLs and endpoints as constants.
- Use proper HTTP status codes to determine success/failure.
- Implement retry logic for transient failures.
- Cache responses when appropriate to reduce network calls.
- Never make network calls in build() method.

10

## Exercises

### 1. Basic API Client

Create an ApiService class that fetches a list of posts from JSONPlaceholder API (https://jsonplaceholder.typicode.com/posts). Create a Post model class with id, title, and body fields. Display the posts in a ListView with proper loading and error states.

### 2. CRUD Operations

Extend the ApiService to support creating, updating, and deleting posts. Add UI buttons to test each operation and show success/error messages.

### 3. Error Handling and Retry

Implement a retry mechanism that attempts the request up to 3 times with exponential backoff. Add a retry button in the error UI that manually retries failed requests.