HTTP POST: Mengirim Data ke API
Kirim data form dan JSON ke server dengan POST, tangani loading dan error saat submit.
POST dengan body JSON
Kebanyakan API modern menerima JSON. Encode map Dart menjadi string JSON dan set header content type:
Future<Produk> tambahProduk({required String nama, required int harga}) async {
final uri = Uri.parse('https://api.contoh.id/produk');
final response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'nama': nama, 'harga': harga}),
);
if (response.statusCode == 201) {
return Produk.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
}
throw Exception('Gagal menambah produk: ${response.statusCode}');
}Status 201 (Created) adalah respons standar untuk POST yang berhasil membuat resource baru.
Pola submit di UI
Saat submit, nonaktifkan tombol dan tampilkan loading agar user tidak menekan dua kali:
class _TambahState extends State<Tambah> {
bool _loading = false;
final _namaController = TextEditingController();
final _hargaController = TextEditingController();
Future<void> _submit() async {
if (_loading) return;
setState(() => _loading = true);
try {
final produk = await tambahProduk(
nama: _namaController.text.trim(),
harga: int.tryParse(_hargaController.text) ?? 0,
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${produk.nama} tersimpan')),
);
Navigator.pop(context, true);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal: $e')),
);
} finally {
if (mounted) setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tambah Produk')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(controller: _namaController, decoration: const InputDecoration(labelText: 'Nama')),
const SizedBox(height: 12),
TextField(controller: _hargaController, decoration: const InputDecoration(labelText: 'Harga'), keyboardType: TextInputType.number),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _loading ? null : _submit,
child: _loading
? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2))
: const Text('Simpan'),
),
),
],
),
),
);
}
}Pola try/catch/finally dengan mounted check adalah template standar untuk setiap aksi async yang menyentuh UI setelahnya.
Validasi sebelum kirim
Jangan mengandalkan validasi server saja. Cek di client dulu: nama tidak kosong, harga lebih dari nol. Ini menghemat request gagal dan memberi feedback instan.
Catatan teknis:
jsonEncodehanya bisa meng-encode tipe dasar (String, num, bool, null, List, Map). Kalau kamu meneruskan objek custom langsung, ia akan throw. Selalu konversi ketoJson()dulu. Dan untuk API yang butuh token, jangan hardcode token di kode, simpan di secure storage dan kirim lewat headerAuthorization: Bearer ....