Flutter Basic Chapter 5 - 웹툰 API 연동과 상세 화면
AppBar와 기본 화면 정비
🎯 이번 단계에서 배울 것
MaterialApp
과runApp
에const
를 적용해 불필요한 리빌드 최소화- 홈 화면을
HomeScreen
StatelessWidget으로 구성하고 한국어 AppBar 추가 - AppBar의
backgroundColor
,foregroundColor
,elevation
속성으로 스타일 제어
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | // lib/main.dart |
1 | // lib/screens/home_screen.dart |
🔍 상세 설명
const MaterialApp
사용으로 위젯 트리를 고정화하고 Hot Reload 성능을 개선했습니다.AppBar
의 그림자 높이를elevation: 2
로 줄여 얇은 실선 느낌을 주고, 글자색은foregroundColor
로 초록색으로 통일했습니다.- 테스트 파일에서
MyApp
대신App
을 pump하도록 수정해 기본 테스트가 통과합니다.
🎨 화면 미리보기
1 | ┌────────────────────────────┐ |
✅ 체크리스트
-
runApp(const App())
로const
가 적용됐는가? - AppBar 배경이 흰색, 텍스트는 초록색으로 표시되는가?
- 기본 테스트(
test/widget_test.dart
)가App
기준으로 동작하는가?
HTTP 요청과 모델 정의 (Data Fetching & fromJson)
🎯 이번 단계에서 배울 것
http
패키지를 의존성으로 추가하고 REST API 호출하기- 비동기 함수에
Future<List<WebtoonModel>>
타입 선언하기 jsonDecode
결과를WebtoonModel.fromJson
으로 변환해 강타입 컬렉션 만들기
📂 파일 구조
1 | 수정되는/생성되는 파일 |
📝 핵심 코드
1 | // pubspec.yaml (dependencies) |
1 | // lib/models/webtoon_model.dart |
1 | // lib/services/api_service.dart |
1 | // lib/main.dart (임시 호출) |
🔍 상세 설명
.fromJson
생성자를 통해 맵 구조 데이터를 안전하게 모델로 변환했습니다.- 5.3 Recap 커밋에서
final instance = ...
변수로 나누어 가독성을 높였습니다. ApiService
를 static 메서드로 재구성(5.4)해 인스턴스를 생성하지 않고도 재사용할 수 있도록 했습니다.
✅ 체크리스트
-
http
패키지가 의존성으로 추가되고flutter pub get
이 실행되었는가? -
getTodaysToons()
가Future<List<WebtoonModel>>
를 반환하는가? - HTTP 200 이외에는
throw Error()
로 예외를 던지는가?
초기 데이터 로딩과 상태 관리 (waitForWebToons)
🎯 이번 단계에서 배울 것
StatefulWidget
에서initState()
를 활용해 비동기 초기화 실행하기- 로딩 상태(
isLoading
)와 결과 리스트(webtoons
)를 상태로 저장하기 - API 호출을 기다린 후
setState()
로 UI 갱신하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | class _HomeScreenState extends State<HomeScreen> { |
🔍 상세 설명
- API 응답을 기다린 뒤
setState()
를 호출해 화면을 다시 그리고, 개발 중에는print
로 값 확인을 했습니다. - 이후 5.5에서
FutureBuilder
로 리팩터링하므로 이 임시 상태 로직은 제거됩니다.
✅ 체크리스트
-
waitForWebToons()
가initState
에서 한 번만 호출되는가? - API 응답이 완료되면
isLoading
이 false로 바뀌는가?
FutureBuilder로 상태 단순화
🎯 이번 단계에서 배울 것
FutureBuilder
를 사용해 비동기 처리 결과를 위젯 트리 안에서 직접 다루기snapshot.hasData
를 기준으로 로딩/성공 상태 분기하기Scaffold
의body
에 FutureBuilder를 배치해 전체 화면 컨텐츠 제어하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | class HomeScreen extends StatelessWidget { |
🔍 상세 설명
FutureBuilder
덕분에 StatefulWidget이 다시 StatelessWidget으로 간결해졌습니다.- 이후 섹션에서
snapshot.data!
를 활용해 리스트를 렌더링합니다.
✅ 체크리스트
-
FutureBuilder
가 null safety에 맞게snapshot.hasData
체크 후 데이터를 사용하는가? - 로딩 중에는 텍스트 또는 스피너가 표시되는가?
가로 스크롤 리스트뷰 구성
🎯 이번 단계에서 배울 것
ListView.separated
를 이용해 카드 간 간격을 쉽게 조절하기scrollDirection: Axis.horizontal
로 가로 캐러셀 형태 만들기- 로딩 상태일 때
CircularProgressIndicator
를 중앙에 배치하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | return FutureBuilder( |
🔍 상세 설명
separatorBuilder
로 일정한 공백(SizedBox(width: 20)
)을 자동 삽입했습니다.CircularProgressIndicator
를 사용해 데이터 로딩 중임을 명확히 전달합니다.
✅ 체크리스트
-
ListView.separated
가 가로 방향으로 동작하는가? - 로딩 시 스피너가 중앙에 표시되는가?
Webtoon 카드 디자인
🎯 이번 단계에서 배울 것
- 썸네일 이미지에 그림자(
BoxShadow
)와 둥근 모서리(borderRadius
) 적용 Column
과Expanded
로 리스트 상단 여백과 공간 비율 조절- 별도 메서드
makeList
로 빌더 코드를 분리해 가독성 향상
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | ListView makeList(AsyncSnapshot<List<WebtoonModel>> snapshot) { |
✅ 체크리스트
- 썸네일이 둥근 모서리와 그림자를 적용한 컨테이너 안에 들어가는가?
- 타이틀 텍스트가 이미지 아래에 표시되는가?
상세 화면과 분리된 위젯 구조
🎯 이번 단계에서 배울 것
Navigator.push
와MaterialPageRoute
로 상세 화면으로 이동하기- 썸네일/타이틀 UI를
Webtoon
위젯으로 분리해 재사용성 확보 - 상세 화면(
DetailScreen
)에서 동일한 카드 레이아웃 재활용
📂 파일 구조
1 | 새로 생성되는 파일 |
📝 핵심 코드
1 | // lib/widgets/webtoon_widget.dart |
1 | // lib/screens/detail_screen.dart (초기 버전) |
🔍 상세 설명
fullscreenDialog: true
로 iOS 스타일의 모달 전환 효과를 적용했습니다.DetailScreen
은 아직 정적 정보만 보여주며, 이후 섹션에서 API 연동으로 확장합니다.
✅ 체크리스트
- 썸네일을 탭하면 DetailScreen으로 이동하는가?
- 홈 화면과 상세 화면 모두 동일한 카드 스타일을 유지하는가?
Hero 애니메이션 연결
🎯 이번 단계에서 배울 것
Hero
위젯을 이용해 썸네일 전환 애니메이션 구현하기tag
로 고유 ID(webtoon.id
)를 사용해 출발/도착 위젯을 연결하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | Hero( |
🔍 상세 설명
- 동일한
tag
값을 가진 Hero 위젯이 화면 전환 시 자연스러운 확대/축소 이동을 제공합니다. - Hero는 반드시 전환 대상 두 화면에 모두 존재해야 하므로 홈/상세 화면 모두 수정을 진행했습니다.
✅ 체크리스트
- 카드 썸네일이 상세 화면으로 전환될 때 부드러운 애니메이션이 재생되는가?
상세 정보 API 확장
🎯 이번 단계에서 배울 것
- 세부 정보용 모델(
WebtoonDetailModel
,WebtoonEpisodeModel
)을 추가로 정의하기 ApiService
에 웹툰 상세 정보/에피소드 목록 API 메서드 구현하기
📂 파일 구조
1 | 새로 생성되는 파일 |
📝 핵심 코드
1 | static Future<WebtoonDetailModel> getToonById(String id) async { |
✅ 체크리스트
- 상세 API가 JSON을 모델 객체로 변환해 반환하는가?
- 에피소드 리스트가
List<WebtoonEpisodeModel>
형태로 준비되는가?
DetailScreen에서 Future 준비
🎯 이번 단계에서 배울 것
DetailScreen
을 StatefulWidget으로 전환하고initState()
에서 Future 초기화widget.id
를 사용해 라우트 인자로 받은 값을 접근하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | class DetailScreen extends StatefulWidget { |
✅ 체크리스트
-
webtoon
과episodes
Futures가late
로 선언되고initState
에서 초기화되는가?
상세 정보 UI 표시
🎯 이번 단계에서 배울 것
FutureBuilder
로 상세 정보를 받아와 소개글/장르/연령을 표시하기Padding
과crossAxisAlignment
로 텍스트 정렬 다듬기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | FutureBuilder( |
✅ 체크리스트
- 상세 소개글과 장르/연령 정보가 로딩 완료 후 표시되는가?
- 로딩 중에는 placeholder(
...
)가 나타나는가?
에피소드 목록과 스크롤 레이아웃
🎯 이번 단계에서 배울 것
SingleChildScrollView
와Padding
으로 상세 화면 전체를 스크롤 가능하게 만들기FutureBuilder
+for
루프 조합으로 에피소드 목록을 위젯 리스트로 변환하기- 카드 형태의 에피소드 요소에 그림자와 간격 적용하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | FutureBuilder( |
✅ 체크리스트
- 상세 화면이 세로로 스크롤되며 이미지/설명/에피소드가 이어지는가?
- 각각의 에피소드 카드가 일정한 간격과 그림자를 갖는가?
URL Launcher로 네이버 웹툰 열기
🎯 이번 단계에서 배울 것
url_launcher
패키지 추가 및 플랫폼 설정(iOS/Android/macOS 등) 적용- 에피소드 전용 위젯(
Episode
) 분리 및 탭 제스처 처리 launchUrlString
으로 외부 브라우저를 통해 실제 웹툰 페이지 열기
📂 파일 구조
1 | 새로 생성되는 파일 |
📝 핵심 코드
1 | // lib/widgets/episode_widget.dart |
1 | // lib/screens/detail_screen.dart (에피소드 출력 부분) |
🔍 플랫폼 설정 요약
- iOS/ macOS
Podfile
,xcconfig
파일에use_frameworks!
등 url_launcher 구성이 추가되었습니다. - Flutter가 자동 생성하는
GeneratedPluginRegistrant
파일들도 url_launcher 등록 코드가 포함되도록 업데이트되었습니다.
✅ 체크리스트
- 에피소드를 탭하면 기본 브라우저에서 네이버 웹툰 상세 페이지가 열리는가?
- url_launcher 의존성이 pubspec에 추가되어 있는가?
즐겨찾기(좋아요) 상태 저장
🎯 이번 단계에서 배울 것
shared_preferences
패키지로 간단한 로컬 스토리지 구현initPrefs()
에서 초기화 후likedToons
리스트를 읽어 상태 반영하기- AppBar 아이콘을
Icons.favorite
↔Icons.favorite_outline
으로 토글하기
📂 파일 구조
1 | 수정되는 파일 |
📝 핵심 코드
1 | class _DetailScreenState extends State<DetailScreen> { |
1 | appBar: AppBar( |
🔍 상세 설명
- 앱 최초 실행 시
likedToons
키가 없으면 빈 리스트를 만들어 저장합니다. - 즐겨찾기 토글 시
setStringList
로 업데이트하고isLiked
를 반전합니다. - Heart 아이콘이 즉시 변경되어 UX가 즉각적입니다.
✅ 체크리스트
- 앱을 재실행해도 즐겨찾기한 웹툰은 하트가 채워진 상태로 표시되는가?
-
shared_preferences
의존성이 pubspec에 추가되었는가?
📚 전체 흐름 정리
- AppBar가 있는 기본 UI를 구성했고, HTTP 요청과 JSON → 모델 변환을 준비했습니다.
- FutureBuilder와 ListView로 오늘의 웹툰 목록을 비동기/가로 스크롤 형태로 렌더링했습니다.
- 카드 UI를 개선하고 상세 화면 이동, Hero 애니메이션을 추가했습니다.
- 상세 정보와 최신 에피소드 목록을 API로 받아와 표시했습니다.
- url_launcher로 네이버 웹툰 페이지를 직접 열 수 있게 했고, shared_preferences로 즐겨찾기 상태를 저장했습니다.
✅ 최종 점검 리스트
-
http
,url_launcher
,shared_preferences
패키지가 pubspec에 포함돼 있는가? - 홈 화면이 가로 캐러셀로 오늘의 웹툰을 보여주고, 탭하면 상세 화면으로 이동하는가?
- 상세 화면에서 소개/장르/연령/최근 에피소드가 제대로 표시되는가?
- 에피소드를 탭하면 외부 브라우저에서 열리는가?
- 하트 아이콘으로 즐겨찾기를 토글할 수 있고 앱을 다시 켜도 상태가 유지되는가?