© Khmer Angkor Academy - sophearithput168

HTTP Requests

HTTP Requests

HTTP ប្រើសម្រាប់ទាញយកទិន្នន័យពី API (Application Programming Interface)។ Flutter ប្រើ http package ដើម្បី communicate ជាមួយ backend servers។

� HTTP & REST API Theory

HTTP Request Lifecycle:

HTTP Request Flow

📱 Flutter App
   ↓
1️⃣ Make Request (GET, POST, PUT, DELETE)
   ↓
🌐 Internet
   ↓
2️⃣ Server receives & processes request
   ↓
3️⃣ Server sends Response (JSON data + status code)
   ↓
4️⃣ Flutter parses JSON → Dart objects
   ↓
5️⃣ Update UI with new data

HTTP Methods (CRUD Operations):

Method CRUD Purpose Example
GET Read ទាញយកទិន្នន័យ Get list of products
POST Create បង្កើតទិន្នន័យថ្មី Create new user
PUT Update ធ្វើបច្ចុប្បន្នភាពទាំងអស់ Update entire profile
PATCH Update ធ្វើបច្ចុប្បន្នភាពមួយផ្នែក Update only email
DELETE Delete លុបទិន្នន័យ Delete a post

HTTP Status Codes:

Code Range Meaning Examples
2xx ✅ Success 200 OK, 201 Created
3xx 🔄 Redirection 301 Moved Permanently
4xx ❌ Client Error 400 Bad Request, 404 Not Found, 401 Unauthorized
5xx 💥 Server Error 500 Internal Server Error, 503 Service Unavailable

�📡 http package (Setup)

# pubspec.yaml
dependencies:
  http: ^1.1.0

🔽 GET Request (Read Data)

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

Future<void> fetchData() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/data')
  );
  
  if (response.statusCode == 200) {
    var data = jsonDecode(response.body);
    print(data);
  }
}

📤 POST Request (Create Data)

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

Future<void> createUser(String name, String email) async {
  try {
    final response = await http.post(
      Uri.parse('https://api.example.com/users'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_TOKEN',
      },
      body: jsonEncode({
        'name': name,
        'email': email,
      }),
    );
    
    if (response.statusCode == 201) {
      print('User created successfully!');
      var data = jsonDecode(response.body);
      print('New user ID: ' + data['id'].toString());
    } else {
      print('Failed to create user: ' + response.statusCode.toString());
    }
  } catch (e) {
    print('Error: ' + e.toString());
  }
}

🔄 PUT/PATCH Request (Update Data)

// PUT - Update entire resource
Future<void> updateUser(int id, Map<String, dynamic> userData) async {
  final response = await http.put(
    Uri.parse('https://api.example.com/users/' + id.toString()),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode(userData),
  );
  
  if (response.statusCode == 200) {
    print('User updated successfully!');
  }
}

// PATCH - Update specific fields
Future<void> updateEmail(int id, String newEmail) async {
  final response = await http.patch(
    Uri.parse('https://api.example.com/users/' + id.toString()),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'email': newEmail}),
  );
}

🗑️ DELETE Request

Future<void> deleteUser(int id) async {
  final response = await http.delete(
    Uri.parse('https://api.example.com/users/' + id.toString()),
    headers: {'Authorization': 'Bearer YOUR_TOKEN'},
  );
  
  if (response.statusCode == 204) {
    print('User deleted successfully!');
  }
}

🎯 Complete Example with Model Class

// Model class
class User {
  final int id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
  
  // Create User from JSON
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
  
  // Convert User to JSON
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }
}

// API Service class
class UserService {
  final String baseUrl = 'https://api.example.com';
  
  Future<List<User>> getUsers() async {
    final response = await http.get(Uri.parse(baseUrl + '/users'));
    
    if (response.statusCode == 200) {
      List jsonData = jsonDecode(response.body);
      return jsonData.map((json) => User.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load users');
    }
  }
  
  Future<User> getUserById(int id) async {
    final response = await http.get(
      Uri.parse(baseUrl + '/users/' + id.toString()),
    );
    
    if (response.statusCode == 200) {
      return User.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('User not found');
    }
  }
}

// Usage in Widget
class UserListPage extends StatefulWidget {
  @override
  _UserListPageState createState() => _UserListPageState();
}

class _UserListPageState extends State<UserListPage> {
  final UserService _userService = UserService();
  List<User> users = [];
  bool isLoading = true;
  
  @override
  void initState() {
    super.initState();
    loadUsers();
  }
  
  Future<void> loadUsers() async {
    try {
      final fetchedUsers = await _userService.getUsers();
      setState(() {
        users = fetchedUsers;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: ' + e.toString())),
      );
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Users')),
      body: isLoading
        ? Center(child: CircularProgressIndicator())
        : ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) {
              final user = users[index];
              return ListTile(
                title: Text(user.name),
                subtitle: Text(user.email),
              );
            },
          ),
    );
  }
}

⚡ Error Handling Best Practices

Future<T> makeRequest<T>(
  Future<http.Response> Function() request,
  T Function(dynamic) parser,
) async {
  try {
    final response = await request();
    
    // Check status code
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return parser(jsonDecode(response.body));
    } else if (response.statusCode == 401) {
      throw Exception('Unauthorized - Please login');
    } else if (response.statusCode == 404) {
      throw Exception('Resource not found');
    } else {
      throw Exception('Error: ' + response.statusCode.toString());
    }
  } on SocketException {
    throw Exception('No internet connection');
  } on TimeoutException {
    throw Exception('Request timeout');
  } on FormatException {
    throw Exception('Invalid response format');
  } catch (e) {
    throw Exception('Unexpected error: ' + e.toString());
  }
}

🔐 Authentication with Headers

class AuthService {
  String? _token;
  
  Map<String, String> get headers {
    return {
      'Content-Type': 'application/json',
      if (_token != null) 'Authorization': 'Bearer ' + _token!,
    };
  }
  
  Future<void> login(String email, String password) async {
    final response = await http.post(
      Uri.parse('https://api.example.com/login'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'email': email, 'password': password}),
    );
    
    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      _token = data['token'];
    }
  }
  
  Future<dynamic> get(String endpoint) async {
    final response = await http.get(
      Uri.parse('https://api.example.com' + endpoint),
      headers: headers,
    );
    
    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
  }
}

💾 Caching Strategy

class CachedApiService {
  final Map<String, dynamic> _cache = {};
  final Duration cacheDuration = Duration(minutes: 5);
  final Map<String, DateTime> _cacheTime = {};
  
  Future<dynamic> getCached(String url) async {
    // Check if cache exists and is valid
    if (_cache.containsKey(url)) {
      final cacheAge = DateTime.now().difference(_cacheTime[url]!);
      if (cacheAge < cacheDuration) {
        print('Using cached data');
        return _cache[url];
      }
    }
    
    // Fetch new data
    final response = await http.get(Uri.parse(url));
    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      _cache[url] = data;
      _cacheTime[url] = DateTime.now();
      return data;
    }
  }
  
  void clearCache() {
    _cache.clear();
    _cacheTime.clear();
  }
}

💡 Best Practices:

  • ✅ Always use try-catch for error handling
  • ✅ Create model classes for JSON parsing
  • ✅ Use separate service classes for API calls
  • ✅ Show loading indicators during requests
  • ✅ Handle different status codes properly
  • ✅ Implement caching for frequently accessed data
  • ✅ Use environment variables for API URLs

⚠️ Common Mistakes:

  • ❌ Not handling errors (app will crash)
  • ❌ Hardcoding API tokens in code (security risk)
  • ❌ Making API calls in build() method (infinite loop)
  • ❌ Not showing loading state to users
  • ❌ Ignoring HTTP status codes

💡 ជំនួយ: ប្រើ async/await សម្រាប់ HTTP requests។ ប្រើ try-catch សម្រាប់ handle errors។ ប្រើ Model classes សម្រាប់ type-safe data។