ํญ๊ณต๊ถ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ๊ฑด๋ฐ, ํ๋ฌํฐ์์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ฉด ํญ๊ณต์ฌ์์ ์ฐจ๋จํ ์ ์์ด์ ๋์ ์ค๊ฐ ์๋ฒ๊ฐ ํ์ํ๋ค.
๋จผ์ ํ์ด์ฌ์ด ์ค์น๋์ด์๋ค๋ ๊ฐ์ ํ์
ํฐ๋ฏธ๋์์ pip install fastapi uvicorn๋ก ์๋ฒ๋ฅผ ๋ง๋๋ ํ๋ ์์ํฌ์ธ FastAPI๋ฅผ ์ค์นํ๋ค.
์ดํ server๋ฅผ ๋ด๋นํ ํ์ผ์ ๋ค๋ฅธ ๊ฒฝ๋ก์ ๋ง๋ค๊ณ (๋ฐ์ดํฐ๋ ์ผ๋จ ํ๋์ฝ๋ฉ)
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/search")
def search(from_city: str = Query(..., alias="from"), to_city: str = Query(..., alias="to"), date: str = Query(...)):
return [
{
"airline": "Jeju Air",
"departTime": "14:45",
"price": 120000
},
{
"airline": "Korean Air",
"departTime": "08:30",
"price": 180000
}
]
์๋ฒ ํ์ผ์ด ์๋ ํด๋์ ํฐ๋ฏธ๋์์
python -m uvicorn server:app --reload
๋ฅผ ์
๋ ฅํ์ฌ ์๋ฒ๋ฅผ ์ด ์ ์๊ณ ,
์ธํฐ๋ท ๋ธ๋ผ์ฐ์ ์
http://127.0.0.1:8000/search?from=ICN&to=NRT&date=2026-01-20&type=depart
์ด๋ฐ์์ผ๋ก ์
๋ ฅํ์ฌ ์์ฒญ์ ํ
์คํธ ํด ๋ณผ ์ ์๋ค.
ํ๋ฌํฐ์์๋ ์ด๋ฐ์์ผ๋ก ๋งค๊ฐ๋ณ์๋ค์ ์ ๋ ฅํ์ฌ ์์ฒญ์ ๋ณด๋ด ํจ์๋ฅผ ํธ์ถํ ๊ฒ์ด๋ค.
ํ๋ฌํฐ์์, ๋จผ์ HTTP ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด์ค๋ค
flutter pub add http
flutter pub get
์ดํ api๋ฅผ ํธ์ถํ ์ฝ๋๋ฅผ ์์ฑํ ๋ค
import 'dart:convert';
import 'package:http/http.dart' as http;
class TicketApi {
static const String baseUrl = "http://127.0.0.1:8000";
static Future<List<dynamic>> searchFlights({
required String from,
required String to,
required String date,
}) async {
final url = Uri.parse(
"$baseUrl/search?from=$from&to=$to&date=$date",
);
final response = await http.get(url);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception("์๋ฒ ์ค๋ฅ: ${response.statusCode}");
}
}
}
๊ฒ์ ๋ฒํผ์ ์ฐ๊ฒฐํด์ฃผ๊ธฐ์ ์์ ์ฌ์ฉ์์ ์ ๋ ฅ์ ์๋ฒ์์ ์ํ๋ ํํ๋ก ๋ณํํด์ฃผ์ด์ผ ํ๋ค.
final Map<String, String> _destinationCode = {
'๋์ฟ': 'NRT',
'์ค์ฌ์นด': 'KIX',
'ํ์ฟ ์ค์นด': 'FUK',
};
String _formatDate(DateTime date) {
return "${date.year.toString().padLeft(4, '0')}-"
"${date.month.toString().padLeft(2, '0')}-"
"${date.day.toString().padLeft(2, '0')}";
}
String get _fromCode {
if (_flightTypeIndex == 0) return "ICN"; // ์ถ๋ฐํธ
return _destinationCode[_selectedDestination]!; // ๋์ฐฉํธ
}
String get _toCode {
if (_flightTypeIndex == 0) return _destinationCode[_selectedDestination]!;
return "ICN";
}
๊ทธ๋ฆฌ๊ณ ์ ์ ๋ง๋ค์๋ TicketResult ํด๋์ค์ ํํ๋ฅผ ๋ณํํ๋ ํจ์๋ฅผ ํ๋ ๋ง๋ค์ด์ฃผ๊ณ ,
factory TicketResult.fromJson(Map<String, dynamic> json) {
return TicketResult(
airline: json['airline'],
departTime: json['departTime'],
price: json['price'],
);
}
์ต์ข ์ ์ผ๋ก ๊ฒ์ ๋ฒํผ์ ์ฐ๊ฒฐํด์ค๋ค.
Future<void> onSearchPressed() async {
try {
final rawList = await TicketApi.searchFlights(
from: _fromCode,
to: _toCode,
date: _formatDate(_selectedDate!),
);
final List<TicketResult> parsed = rawList
.map((json) => TicketResult.fromJson(json))
.toList();
// ๊ฐ๊ฒฉ ์ค๋ฆ์ฐจ์ ์ ๋ ฌ
parsed.sort((a, b) => a.price.compareTo(b.price));
setState(() {
_results = parsed;
});
}
catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค"),
backgroundColor: Colors.red,
),
);
}
}
๊ทธ๋ฆฌ๊ณ ๊ฒ์ ๋ฒํผ์ ๋๋ฌ๋ณด๋ฉด... ํธ์ถ์ด ๋์ง ์๋๋ค.
์ด์ ๋ PC์ ๋ก์ปฌํธ์คํธ๋ 127.0.0.1:8000์ด ๋ง์ง๋ง, ์๋๋ก์ด๋ ์๋ฎฌ๋ ์ดํฐ๋ 10.0.2.2:8000์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ผ TicketApi์ Url ์ฝ๋๋ฅผ
static String get baseUrl {
// ์น ๋ธ๋ผ์ฐ์ ์์ ์คํ ์ค
if (kIsWeb) {
return "http://127.0.0.1:8000";
}
// ์๋๋ก์ด๋ (์๋ฎฌ๋ ์ดํฐ or ์ค์ ํฐ)
if (Platform.isAndroid) {
return "http://10.0.2.2:8000";
}
// iOS ์๋ฎฌ๋ ์ดํฐ, macOS, Windows
return "http://127.0.0.1:8000";
}
ํ๋ซํผ์ ๋ง๊ฒ ๋์์์ผ์ฃผ๋ฉด server.py์ ํ๋์ฝ๋ฉํ ๋ฐ์ดํฐ๊ฐ ๋ถ๋ฌ์์ง ๋ชจ์ต์ด๋ค.

* ์ถ๊ฐ๋ก ๋ ์ง๋ฅผ ์ ๋ ฅํ์ง ์์ผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ์ฌ ๊ธฐ๋ณธ์ ์ผ๋ก ์ค๋ ๋ ์ง๊ฐ ์ ํ๋๋๋ก ์์ ํ๋ค.
/// ๋ ์ง ์ ํ
Widget _buildDateButton() {
return ElevatedButton(
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (pickedDate != null) {
setState(() {
_selectedDate = pickedDate;
});
}
},
child: Text(
_selectedDate == null ? '${DateTime.now().month}/${DateTime.now().day}' : '${_selectedDate!.month}/${_selectedDate!.day}',
),
);
}