main.dart


import 'package:flutter/material.dart';
import 'package:webtoon_app/screens/home_screen.dart';
import 'package:webtoon_app/services/api_service.dart';

void main() {
  ApiService().getTodaysToons();
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key}); // 이 위젯의 key를 stateless widget이라는 슈퍼클래스에 보낸다.
  // 위젯은 key라는 걸 가지고 있고 ID처럼 쓰인다.
  @override
  Widget build(BuildContext context) {
    return  MaterialApp( // home_screen.dart가 api 요청을 받아오기 때문에 const일 수 없음
      home: HomeScreen(),
    );
  }
}

services/api_service.dart


import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:webtoon_app/models/webtoon_model.dart';

class ApiService {
  static const String baseUrl =
      "<https://webtoon-crawler.nomadcoders.workers.dev>";
  static const String today = "today";
  // api요청을 하기 위해서 웹에서 fetch나 axios를 사용하는 것처럼 http 모듈을 사용해주자!

  Future<List<WebtoonModel>> getTodaysToons() async {
    List<WebtoonModel> webtoonInstances = [];
    final url = Uri.parse('$baseUrl/$today');
    final response = await http.get(url);
    // 데이터가 올 때까지 잠깐 멈춰야될 때가 있을 때 사용한다.
    // await으로 future가 완료될 때까지 기다린다.
    if (response.statusCode == 200) {
      final List<dynamic> webtoons = jsonDecode(response.body);
      // 텍스트로 응답된 body를 JSON으로 디코딩해준다.
      // dynamic으로 반환값이 나와서 type지정해주는게 좋음
      for (var webtoon in webtoons) {
        final toon = WebtoonModel.fromJson(webtoon);
        webtoonInstances.add(toon);
        print(webtoon);
      }
      return webtoonInstances;
    }
    throw Error();
  }
}

models/webtoon_model.dart


class WebtoonModel {
  final String title, thumb, id;

  WebtoonModel.fromJson(Map<String, dynamic> json)
      : title = json['title'],
        thumb = json['thumb'],
        id = json['id'];
  // Map<String, dynamic>은 TS로 따지면 key값이 String value값이 any이다.
  // named constructor라고 하고 이름이 있는 클래스 constructor
}

screens/home_screen.dart


import 'package:flutter/material.dart';
import 'package:webtoon_app/models/webtoon_model.dart';
import 'package:webtoon_app/services/api_service.dart';

class HomeScreen extends StatelessWidget {
  HomeScreen({super.key});
  // API를 받아오기 때문에 const일 수가 없다.
  // const는 컴파일 전에 값을 알고 있다는 뜻

  Future<List<WebtoonModel>> webtoons = ApiService().getTodaysToons();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        elevation: 2,
        title: const Text("오늘의 웹툰",
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.w500)),
        backgroundColor: Colors.white,
        foregroundColor: Colors.green,
      ),
      body: FutureBuilder(
        future: webtoons,
        builder: (context, snapshot) {
          // snapshot을 사용하면 Future의 상태를 알 수 있다.
          // snapshot을 통해서 변화를 알려준다.
          if (snapshot.hasData) {
            return const Text("There is data!");
          }
          return const Text("Loading...");
        },
      ), // webtoons를 가져오는 api 요청을 기다려달라하자
    );
  }
}

FutureBuilder 위젯을 사용하면 더 적은 코드로 손 쉽게 Loading 상태인 경우와 API요청을 보내고 반환 값을 받아왔을 때인 경우를 쉽게 구분하고 더욱 쉽게 렌더링 할 수 있다.