Back to Posts

Flutter HTTP: How to Fetch Data from the Internet

7 min read
<div style="text-align: center;"> <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDMwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPCEtLSBIVFRQIFJlcXVlc3QgRXhhbXBsZSAtLT4KICA8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI0ZGRiIgc3Ryb2tlPSIjMDAwIi8+CiAgPHRleHQgeD0iMTUwIiB5PSIxMDAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzIxMjEyMSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+SFRUUCBSZXF1ZXN0PC90ZXh0Pgo8L3N2Zz4=" alt="HTTP Request Example" width="300" /> </div>

This comprehensive guide will walk you through fetching and handling data from the internet in Flutter applications. Learn how to make HTTP requests, parse responses, and implement best practices for data handling.

Getting Started

1. Add Dependencies

dependencies:
  http: ^0.13.4
  dio: ^4.0.0

2. Basic HTTP Request

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

Future<void> fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com/data'));
  
  if (response.statusCode == 200) {
    // Parse the response
    print(response.body);
  } else {
    throw Exception('Failed to load data');
  }
}

HTTP Methods

1. GET Request

Future<Map<String, dynamic>> getData() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/data'),
    headers: {'Authorization': 'Bearer token'},
  );
  
  if (response.statusCode == 200) {
    return json.decode(response.body);
  }
  throw Exception('Failed to load data');
}

2. POST Request

Future<void> postData(Map<String, dynamic> data) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/data'),
    headers: {'Content-Type': 'application/json'},
    body: json.encode(data),
  );
  
  if (response.statusCode == 201) {
    print('Data posted successfully');
  } else {
    throw Exception('Failed to post data');
  }
}

3. PUT Request

Future<void> updateData(String id, Map<String, dynamic> data) async {
  final response = await http.put(
    Uri.parse('https://api.example.com/data/$id'),
    headers: {'Content-Type': 'application/json'},
    body: json.encode(data),
  );
  
  if (response.statusCode == 200) {
    print('Data updated successfully');
  } else {
    throw Exception('Failed to update data');
  }
}

4. DELETE Request

Future<void> deleteData(String id) async {
  final response = await http.delete(
    Uri.parse('https://api.example.com/data/$id'),
  );
  
  if (response.statusCode == 204) {
    print('Data deleted successfully');
  } else {
    throw Exception('Failed to delete data');
  }
}

Data Parsing

1. JSON Parsing

class User {
  final int id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
  };
}

2. List Parsing

List<User> parseUsers(String responseBody) {
  final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
  return parsed.map<User>((json) => User.fromJson(json)).toList();
}

Error Handling

1. Basic Error Handling

Future<void> fetchData() async {
  try {
    final response = await http.get(Uri.parse('https://api.example.com/data'));
    
    if (response.statusCode == 200) {
      // Handle success
    } else {
      throw HttpException('Failed to load data: ${response.statusCode}');
    }
  } catch (e) {
    // Handle error
    print('Error: $e');
  }
}

2. Custom Exception

class ApiException implements Exception {
  final String message;
  final int statusCode;
  
  ApiException(this.message, this.statusCode);
  
  @override
  String toString() => 'ApiException: $message (Status: $statusCode)';
}

State Management

1. Using Provider

class DataProvider extends ChangeNotifier {
  List<User> _users = [];
  bool _isLoading = false;
  String _error = '';
  
  List<User> get users => _users;
  bool get isLoading => _isLoading;
  String get error => _error;
  
  Future<void> fetchUsers() async {
    _isLoading = true;
    notifyListeners();
    
    try {
      final response = await http.get(Uri.parse('https://api.example.com/users'));
      if (response.statusCode == 200) {
        _users = parseUsers(response.body);
        _error = '';
      } else {
        throw ApiException('Failed to load users', response.statusCode);
      }
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

2. Using Bloc

class DataBloc extends Bloc<DataEvent, DataState> {
  DataBloc() : super(DataInitial()) {
    on<FetchData>(_onFetchData);
  }
  
  Future<void> _onFetchData(FetchData event, Emitter<DataState> emit) async {
    emit(DataLoading());
    try {
      final response = await http.get(Uri.parse('https://api.example.com/data'));
      if (response.statusCode == 200) {
        emit(DataLoaded(parseData(response.body)));
      } else {
        emit(DataError('Failed to load data'));
      }
    } catch (e) {
      emit(DataError(e.toString()));
    }
  }
}

Best Practices

1. API Client

class ApiClient {
  final http.Client _client;
  final String _baseUrl;
  
  ApiClient({http.Client? client, required String baseUrl})
      : _client = client ?? http.Client(),
        _baseUrl = baseUrl;
  
  Future<Map<String, dynamic>> get(String endpoint) async {
    final response = await _client.get(Uri.parse('$_baseUrl/$endpoint'));
    return _handleResponse(response);
  }
  
  Map<String, dynamic> _handleResponse(http.Response response) {
    if (response.statusCode == 200) {
      return json.decode(response.body);
    }
    throw ApiException('Request failed', response.statusCode);
  }
}

2. Caching

class CachedApiClient {
  final ApiClient _apiClient;
  final Map<String, dynamic> _cache = {};
  
  CachedApiClient(this._apiClient);
  
  Future<Map<String, dynamic>> get(String endpoint) async {
    if (_cache.containsKey(endpoint)) {
      return _cache[endpoint];
    }
    
    final data = await _apiClient.get(endpoint);
    _cache[endpoint] = data;
    return data;
  }
}

3. Retry Logic

Future<T> retry<T>(
  Future<T> Function() function, {
  int maxAttempts = 3,
  Duration delay = const Duration(seconds: 1),
}) async {
  for (var i = 0; i < maxAttempts; i++) {
    try {
      return await function();
    } catch (e) {
      if (i == maxAttempts - 1) rethrow;
      await Future.delayed(delay);
    }
  }
  throw Exception('Max attempts reached');
}

Common Issues and Solutions

1. SSL Certificate

class CustomHttpClient extends http.BaseClient {
  final http.Client _client = http.Client();
  
  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    // Handle SSL certificate issues
    return _client.send(request);
  }
}

2. Timeout Handling

Future<void> fetchData() async {
  try {
    final response = await http.get(
      Uri.parse('https://api.example.com/data'),
    ).timeout(
      const Duration(seconds: 10),
      onTimeout: () {
        throw TimeoutException('Request timed out');
      },
    );
    
    // Handle response
  } catch (e) {
    // Handle error
  }
}

3. Network Connectivity

Future<bool> checkConnectivity() async {
  try {
    final result = await InternetAddress.lookup('example.com');
    return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
  } on SocketException catch (_) {
    return false;
  }
}

Conclusion

Fetching data from the internet in Flutter requires proper implementation of HTTP requests, error handling, and state management. Remember to:

  • Use appropriate HTTP methods
  • Handle errors gracefully
  • Implement proper state management
  • Follow best practices
  • Test thoroughly

Happy coding!