<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" />

아래는 Part 1에서 이어지는 내용

</aside>

Data Fetching


객체지향 언어인 Dart는 거의 대부분이 클래스 기반으로 구성된다. API 호출, 응답 데이터 타입 정의, 데이터 처리 역시 모두 클래스 내부에서 이루어진다.

먼저 응답 데이터를 나타내는 모델 클래스를 정의한다. 일반적으로 fromJson 이라는 이름 있는 생성자를 사용하여 JSON 데이터를 인스턴스화 시킨다. 아래 예시에선 JSON 객체를 인자로 받기 때문에 초기화 리스트를 사용하여 클래스 필드에 할당하고 있다.

class WebtoonModel {
  final String id, title, thumb;

  WebtoonModel.fromJson(Map<String, dynamic> json)
      : title = json['title'],
        thumb = json['thumb'],
        id = json['id'];
}

Dart의 네트워크 호출은 주로 http 패키지를 사용한다. 브라우저, Node.js에서 사용할 수 있는 fetch 함수와 비슷하다고 보면 된다. pubspec.yaml 파일 dependencies 목록에 http: 버전 을 추가하면, IDE가 자동으로 인식하여 의존성 추가 여부 팝업이 표시된다. 혹은 Pub get 버튼을 눌러서 바로 추가할 수도 있다. 패키지를 제거할 때도 dependencies 목록에서 지운 후 Pub get 버튼을 누르면 된다.

20240908_141555.png

<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" />

pub get: 패키지 다운로드/설치 (최초 프로젝트 설정 혹은 패키지 추가 후 사용)

pub upgrade: 사용 중인 패키지들을 가능한 최신 버전으로 업그레이드

pub outdated: 사용 중인 패키지들의 현재 버전과 업그레이드 가능한 버전을 비교해서 표시

</aside>

그 후 데이터를 불러오고, 불러온 데이터를 처리하는 API 서비스 클래스를 정의한다. BaseUrl과 같은 값은 변경되지 않기 때문에 const 키워드를 사용하여 컴파일 상수로 선언한다. 이러한 값은 여러 인스턴스에서 동일하게 사용하므로 클래스의 정적(static) 필드로 정의하는게 적합하다. 데이터 패칭 로직은 별도의 메서드에 작성한다.

import 'dart:convert';

import 'package:http/http.dart' as http;

import '../models/webtoon_model.dart';

class ApiService {
  static const String baseUrl = 'webtoon-crawler.nomadcoders.workers.dev';
  static const String today = "today";

	// 오늘의 웹툰 목록을 불러오는 메서드. async 키워드를 붙였기 때문에 Future를 반환한다. 
  static Future<List<WebtoonModel>> getTodayToons() async {
    try {
      // baseUrl과 today를 사용해 HTTPS URI 생성
      // Uri.parse(...)로도 가능(이땐 주소에 프로토콜까지 모두 명시)
      final uri = Uri.https(baseUrl, '/$today');
      // 생성한 URI로 GET 요청을 보내고 응답 대기
      final response = await http.get(uri);

      if (response.statusCode == 200) {
        // String으로 받은 응답 데이터를 JSON으로 디코딩. json.decode(...)로도 가능
        final List<dynamic> webtoons = jsonDecode(response.body);
        // 디코딩된 각 JSON 데이터를 WebtoonModel 인스턴스로 매핑하여 리스트로 반환
        return webtoons.map((data) => WebtoonModel.fromJson(data)).toList();
      } else {
        // 상태 코드가 200이 아닐 경우, 예외 발생
        throw Exception('Failed to load webtoons: ${response.statusCode}');
      }
    } on http.ClientException catch (e) {
      // HTTP 클라이언트 예외 발생 시
      throw Exception('Failed to fetch webtoons: $e');
    } on Exception catch (e) {
      // 기타 예외 발생 시
      throw Exception('An unknown error occurred: $e');
    }
  }
}

// 사용 예시. 콘솔 출력: [Instance of 'WebtoonModel', ...]
ApiService.getTodayToons().then(print);

Uri.https 생성자를 사용하면 프로토콜이 포함된 URI 주소를 생성한다. 첫 번째 인자는 보통 도메인 주소를, 두번째 인자는 리소스에 대한 경로(path)를 지정한다. 이때 경로 가장 앞에는 / 슬래시를 붙여야 하고, 내부적으로 자동 인코딩 되기 때문에, 인코딩되지 않은 주소를 넘겨야 한다.

on 예외유형 구문은 특정 예외 유형을 지정할 때 사용한다. 예외 객체가 필요할 땐 on 예외유형 catch 구문을 사용한다. 일치하는 예외 유형이 없으면 다음 블록으로 넘어가고, 예외를 처리할 블록이 없을 경우엔 프로그램이 종료되기 때문에 일반 catch 블록도 함께 지정해주는 것이 좋다.

데이터 패칭/처리 과정을 정리하면 아래와 같다.

  1. URI 생성 : Uri.https()
  2. 네트워크 요청 : http.get()
  3. 문자열을 JSON으로 변환 : jsonDecode()
  4. JSON 각 항목을 데이터 모델로 맵핑 후 리스트로 반환