การดึงข้อมูลด้วย http
ใน React คุณจะใช้ fetch หรือ axios สำหรับการทำ HTTP requests — ทั้งสองแบบนี้มีอยู่แล้วใน browser หรือติดตั้งได้ด้วย package เดียว Flutter ไม่มี HTTP client ในตัว ดังนั้น ecosystem จึงใช้ http package จากทีม Dart เป็นมาตรฐาน mental model เหมือนกันทุกประการ: ส่ง request, await response, ตรวจสอบ status code, และ decode body ความแตกต่างอยู่ที่ API surface เท่านั้น
เปรียบเทียบแบบ side-by-side
หัวข้อที่มีชื่อว่า “เปรียบเทียบแบบ side-by-side”หมายเหตุ: การเรียก network จริงอาจถูกบล็อกใน DartPad sandbox — รันตัวอย่าง
httpจริงบนเครื่องของคุณหลังจากเพิ่มhttp: ^1.2.0ใต้dependencies:ในpubspec.yaml
// React — fetch (built-in) or axiosimport axios from 'axios';
// Using fetchasync function getUser(id) { const res = await fetch(`https://api.example.com/users/${id}`); if (!res.ok) throw new Error(`HTTP error: ${res.status}`); const data = await res.json(); return data;}
// Using axios (throws automatically on non-2xx)async function getUserAxios(id) { const { data } = await axios.get( `https://api.example.com/users/${id}` ); return data;}// Flutter — package:httpimport 'dart:convert';import 'package:http/http.dart' as http;
Future<Map<String, dynamic>> getUser(int id) async { final uri = Uri.parse( 'https://api.example.com/users/$id', ); final response = await http.get(uri);
if (response.statusCode != 200) { throw Exception('HTTP error: ${response.statusCode}'); }
// response.body is a String — decode manually final data = jsonDecode(response.body) as Map<String, dynamic>; return data;}ตัวอย่างที่รันได้จริง
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้จริง”ตัวอย่าง DartPad ด้านล่างข้ามการเรียก network จริง และใช้ Future.delayed เพื่อจำลอง async latency แทน — เหมือนกับที่คุณ mock API ใน Jest test ตรรกะการ decode JSON ด้วย dart:convert เป็นโค้ดจริงทั้งหมด
import 'dart:convert';
import 'package:flutter/material.dart';
// Simulates a network call — returns after 1 second
// In production replace this with http.get(uri)
Future<Map<String, dynamic>> fetchUser() {
return Future.delayed(
const Duration(seconds: 1),
() => jsonDecode('{"id": 1, "name": "Ada Lovelace", "email": "[email protected]"}')
as Map<String, dynamic>,
);
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HTTP Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const UserScreen(),
);
}
}
class UserScreen extends StatelessWidget {
const UserScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Fetch User'),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
body: FutureBuilder<Map<String, dynamic>>(
future: fetchUser(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text(
'Error: ${snapshot.error}',
style: const TextStyle(color: Colors.red),
),
);
}
final user = snapshot.data!;
return Center(
child: Card(
margin: const EdgeInsets.all(24),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 28,
vertical: 24,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: Colors.indigo,
child: Text(
'${user['name'][0]}',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 12),
Text(
user['name'] as String,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
_Row(label: 'ID', value: '${user['id']}'),
const SizedBox(height: 8),
_Row(label: 'Email', value: user['email'] as String),
],
),
),
),
);
},
),
);
}
}
class _Row extends StatelessWidget {
final String label;
final String value;
const _Row({required this.label, required this.value});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 56,
child: Text(
'$label:',
style: const TextStyle(
fontWeight: FontWeight.w600,
color: Colors.indigo,
),
),
),
Expanded(child: Text(value)),
],
);
}
}