first
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/// HTTP client placeholder that talks to a public sample API.
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../models/item.dart';
|
||||
|
||||
/// Fetches catalog items from JSONPlaceholder.
|
||||
///
|
||||
/// This is intentionally a thin, replaceable client. Swap [baseUrl] and
|
||||
/// [Item.fromJson] when you point the app at a real backend.
|
||||
class ItemApi {
|
||||
ItemApi({http.Client? client, this.baseUrl = defaultBaseUrl})
|
||||
: _client = client ?? http.Client();
|
||||
|
||||
static const defaultBaseUrl = 'https://jsonplaceholder.typicode.com';
|
||||
|
||||
final http.Client _client;
|
||||
final String baseUrl;
|
||||
|
||||
/// GET `/posts` and map each record to an [Item].
|
||||
Future<List<Item>> fetchItems({int limit = 12}) async {
|
||||
final uri = Uri.parse('$baseUrl/posts');
|
||||
final response = await _client.get(
|
||||
uri,
|
||||
headers: const {'Accept': 'application/json'},
|
||||
);
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw ItemApiException(
|
||||
'Request failed (${response.statusCode})',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded is! List) {
|
||||
throw const ItemApiException('Unexpected response shape');
|
||||
}
|
||||
|
||||
return decoded
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.take(limit)
|
||||
.map(Item.fromJson)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
void close() => _client.close();
|
||||
}
|
||||
|
||||
class ItemApiException implements Exception {
|
||||
const ItemApiException(this.message, {this.statusCode});
|
||||
|
||||
final String message;
|
||||
final int? statusCode;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
Reference in New Issue
Block a user