본문 바로가기
Flutter

[Flutter] 공공데이터 api 활용해 xml 데이터 가져오기

by 김무스비 2025. 1. 29.
728x90
반응형

오늘은 공공 데이터 포털 API를 활용해 xml 타입 데이터를 가져와 flutter로 구현해보겠습니다.


오늘 샘플로 가져올 공공 데이터는 인천국제공항공사_여객편 운항현황(다국어) 입니다.(https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15095093)

 

1. 데이터 탐색하기

데이터 미리보기를 통해 한번 살펴보면, 다음과 같이 구성되어있습니다. 

더보기
<response>
<script/>
<script/>
<header>
<resultCode>00</resultCode>
<resultMsg>NORMAL SERVICE.</resultMsg>
</header>
<body>
<items>
<item>
<airline>세부퍼시픽항공</airline>
<airport>마닐라</airport>
<airportCode>MNL</airportCode>
<carousel>8</carousel>
<cityCode>MNL</cityCode>
<elapsetime>0331</elapsetime>
<estimatedDateTime>0001</estimatedDateTime>
<exitnumber>B</exitnumber>
<firstopover/>
<flightId>5J188</flightId>
<gatenumber>112</gatenumber>
<masterflightid/>
<remark>도착</remark>
<scheduleDateTime>2315</scheduleDateTime>
<secstopover/>
<terminalId>P02</terminalId>
<thistopover/>
<typeOfFlight>I</typeOfFlight>
</item>
<item>
<airline>제주항공</airline>
<airport>제주</airport>
<airportCode>CJU</airportCode>
<carousel>D02</carousel>
<cityCode>CJU</cityCode>
<elapsetime>0101</elapsetime>
<estimatedDateTime>0044</estimatedDateTime>
<exitnumber>국내선</exitnumber>
<firstopover/>
<flightId>7C062</flightId>
<gatenumber>1</gatenumber>
<masterflightid/>
<remark>도착</remark>
<scheduleDateTime>2330</scheduleDateTime>
<secstopover/>
<terminalId>P01</terminalId>
<thistopover/>
<typeOfFlight>D</typeOfFlight>
</item>

→  item을 돌면서 값을 가져오면 될 것 같습니다.

→  여기서 주의할 점이 하나 있는데,  값이 혹시나 존재하지 않는 경우에 대한 처리를 해줘야 데이터 읽는 데 문제가 없다는 겁니다. (이 부분 때문에 시간을 꽤나 날렸습니다)

 

2. XML 데이터 파싱 & DART 객체로 변환

xml 플러그인을 pubspec.yaml 파일에 추가해주고, XML 데이터 파싱과 DART 객체로의 변환을 위한 모델 클래스를 하나 생성하겠습니다.

2.1 헬퍼 함수 쓰지 않는 경우 코드

더보기


import 'package:xml/xml.dart';

class Flight {
  final String? airline;
  final String? airport;
  final String? airportCode;
  final String? carousel;
  final String? cityCode;
  final String? codeshare;
  final String? elapsetime;
  final String? estimatedDateTime;
  final String? exitnumber;
  final String? firstopover;
  final String? flightId;
  final String? gatenumber;
  final String? masterflightid;
  final String? remark;
  final String? scheduleDateTime;
  final String? secstopover;
  final String? terminalId;
  final String? thistopover;
  final String? typeOfFlight;

  Flight({
    required this.airline,
    required this.airport,
    required this.airportCode,
    required this.carousel,
    required this.cityCode,
    required this.codeshare,
    required this.elapsetime,
    required this.estimatedDateTime,
    required this.exitnumber,
    required this.firstopover,
    required this.flightId,
    required this.gatenumber,
    required this.masterflightid,
    required this.remark,
    required this.scheduleDateTime,
    required this.secstopover,
    required this.terminalId,
    required this.thistopover,
    required this.typeOfFlight,
  });

  factory Flight.fromXml(XmlElement xml) {
    return Flight(
      airline: xml.findElements('airline').isNotEmpty
          ? xml.findElements('airline').first.innerText
          : null,
      airport: xml.findElements('airport').isNotEmpty
          ? xml.findElements('airport').first.innerText
          : null,
      airportCode: xml.findElements('airportCode').isNotEmpty
          ? xml.findElements('airportCode').first.innerText
          : null,
      carousel: xml.findElements('carousel').isNotEmpty
          ? xml.findElements('carousel').first.innerText
          : null,
      cityCode: xml.findElements('cityCode').isNotEmpty
          ? xml.findElements('cityCode').first.innerText
          : null,
      codeshare: xml.findElements('codeshare').isNotEmpty
          ? xml.findElements('codeshare').first.innerText
          : null,
      elapsetime: xml.findElements('elapsetime').isNotEmpty
          ? xml.findElements('elapsetime').first.innerText
          : null,
      estimatedDateTime: xml.findElements('estimatedDateTime').isNotEmpty
          ? xml.findElements('estimatedDateTime').first.innerText
          : null,
      exitnumber: xml.findElements('exitnumber').isNotEmpty
          ? xml.findElements('exitnumber').first.innerText
          : null,
      flightId: xml.findElements('flightId').isNotEmpty
          ? xml.findElements('flightId').first.innerText
          : null,
      firstopover: xml.findElements('firstopover').isNotEmpty
          ? xml.findElements('firstopover').first.innerText
          : null,
      gatenumber: xml.findElements('gatenumber').isNotEmpty
          ? xml.findElements('gatenumber').first.innerText
          : null,
      masterflightid: xml.findElements('masterflightid').isNotEmpty
          ? xml.findElements('masterflightid').first.innerText
          : null,
      remark: xml.findElements('remark').isNotEmpty
          ? xml.findElements('remark').first.innerText
          : null,
      scheduleDateTime: xml.findElements('scheduleDateTime').isNotEmpty
          ? xml.findElements('scheduleDateTime').first.innerText
          : null,
      secstopover: xml.findElements('secstopover').isNotEmpty
          ? xml.findElements('secstopover').first.innerText
          : null,
      terminalId: xml.findElements('terminalId').isNotEmpty
          ? xml.findElements('terminalId').first.innerText
          : null,
      thistopover: xml.findElements('thistopover').isNotEmpty
          ? xml.findElements('thistopover').first.innerText
          : null,
      typeOfFlight: xml.findElements('typeOfFlight').isNotEmpty
          ? xml.findElements('typeOfFlight').first.innerText
          : null,
    );
  }
}

 

2.2 헬퍼 함수 쓴 경우 코드

더보기

import 'package:xml/xml.dart';

class Flight {
  final String? airline;
  final String? airport;
  final String? airportCode;
  final String? carousel;
  final String? cityCode;
  final String? codeshare;
  final String? elapsetime;
  final String? estimatedDateTime;
  final String? exitnumber;
  final String? flightId;
  final String? gatenumber;
  final String? remark;
  final String? scheduleDateTime;
  final String? terminalId;
  final String? typeOfFlight;

  Flight({
    required this.airline,
    required this.airport,
    required this.airportCode,
    required this.carousel,
    required this.cityCode,
    required this.codeshare,
    required this.elapsetime,
    required this.estimatedDateTime,
    required this.exitnumber,
    required this.flightId,
    required this.gatenumber,
    required this.remark,
    required this.scheduleDateTime,
    required this.terminalId,
    required this.typeOfFlight,
  });

  factory Flight.fromXml(XmlElement xml) {
    // 헬퍼 함수 정의
    String? getValue(String tag) {
      final element = xml.findElements(tag).firstWhere( //요소들 중 첫 번째로 innerText가 비어있지 않은 요소 찾기
          (e) => e.innerText.isNotEmpty, // 요소의 내용이 비어있지 않은지 확인합니다.
          orElse: () => XmlElement(XmlName(tag))); // 요소를 찾지 못하면 빈 요소를 반환합니다.
      return element.innerText.isNotEmpty ? element.innerText : null; // 요소의 내용이 비어있지 않으면 내용을 반환하고, 그렇지 않으면 null을 반환합니다.
    }

    // Flight 객체 생성 및 반환
    return Flight(
      airline: getValue('airline'),
      airport: getValue('airport'),
      airportCode: getValue('airportCode'),
      carousel: getValue('carousel'),
      cityCode: getValue('cityCode'),
      elapsetime: getValue('elapsetime'),
      estimatedDateTime: getValue('estimatedDateTime'),
      exitnumber: getValue('exitnumber'),
      flightId: getValue('flightId'),
      gatenumber: getValue('gatenumber'),
      remark: getValue('remark'),
      codeshare: getValue('codeshare'),
      scheduleDateTime: getValue('scheduleDateTime'),
      terminalId: getValue('terminalId'),
      typeOfFlight: getValue('typeOfFlight'),
    );
  }
}

1)변수&생성자 선언을 해줍니다.

2)XML데이터로부터 Flight 객체를 생성하는 팩토리 생성자를 만들어줍니다. 이 때, 혹시 모를 경우를 대비해 isNotEmpty로 체크를 해줍니다. 

3)first.innerText로 해당 태그의 값을 가져옵니다. (xml 플러그인 버전 6.3.0부터 XmlNode.text가 deprecated되었습니)

 

 

 

3. API를 활용해 데이터를 가져오기

공공 데이터 포털 API와 방금 만든 Flight 객체를 사용해 데이터를 가져와보겠습니다.

상세페이지의 요청 변수를 보면, 인증키를 제외한 나머지 항목들은 옵션이라 안 넣어줘도 될 것 같습니다.

 

요청할 주소는 해당 API 활용 가이드 문서에 담겨있어 그대로 복붙해주면 될 것 같습니다. 저는 인증키 부분만 넣고, 나머지 파라미터는 지웠습니다. 

 

더보기

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:xml/xml.dart';
import 'flightmodel.dart'; // 모델 클래스를 정의한 파일

class ApiService {
  static const String baseUrl =

  static Future<List<Flight>> getFlights() async {
    final url = Uri.parse(baseUrl);
    final response = await http.get(url);

    if (response.statusCode == 200) {
      final responseBody =
          utf8.decode(response.bodyBytes); // 응답 본문을 UTF-8로 디코딩합니다.

      final document = XmlDocument.parse(responseBody); // XML 문서를 파싱합니다.
      final items = document.findAllElements('item');
      return items
          .map((xml) => Flight.fromXml(xml))
          .toList(); // 각 'item' 태그를 Flight 객체로 변환합니다.
    } else {
      throw Exception('Failed to load flights');
    }
  }
}

1)비동기 함수에 Uri.parse한 url을 선언해줍니다.

2)해당 url로 요청을 보내고 데이터를 받은 뒤 한글때문에 본문이 깨지는 경우가 있어서 response를 utf8.decode 해줍니다.

3)XmlDocument.parse로 XML 문서를 파싱해주고, item이 들어간 모든 elements들을 찾아줍니다.

4)2번 과정에서 만들었던 Flight 객체를 활용해 변환해주고, list형태로 저장해줍니다.

 

4. 데이터를 활용해 화면으로 렌더링하기

받아온 데이터를 간단한 listview 형태로 렌더링해줍니다.

더보기


import 'package:flightapp/model/apiservice.dart';
import 'package:flightapp/model/flightmodel.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  late Future<List<Flight>> flights;

  @override
  void initState() {
    super.initState();
    flights = ApiService.getFlights();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flight Data Example'),
      ),
      body: FutureBuilder<List<Flight>>(
        future: flights,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return const Center(child: Text('No flights found'));
          } else {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final flight = snapshot.data![index];
                return ListTile(
                  title: Text('Flight ID: ${flight.flightId}'),
                  subtitle: Text(
                      'Airline: ${flight.airline}, Airport: ${flight.airport}'),
                );
              },
            );
          }
        },
      ),
    );
  }
}

 

최종 결과는 첨부한 화면과 같습니다. 

끝. 감사합니다.

json 데이터 파싱 편으로 또 찾아오겠습니다.

728x90
반응형