자 이제 마지막 웹툰앱 만들기 과정만 남았다. 지금까지 배운 내용들을 종합적으로 사용하면서 인터넷에서 패키지를 다운 받아서 사용하는 방법과 스마트폰의 API사용하는 방법, 그리고 폰에 데이터를 저장하는 방법 등에 대해서 다룰 거다.
API는 네이버웹툰의 API를 사용할 거임!
따오고 싶은 웹툰을 보여준 API로 요청하면 이렇게 제목, 설명, 장르 그리고 연령 제한까지 응답받을 수 있다. 성인용 웹툰도 있기 때문에 이는 제외하고 제작한다고 한다!
지민이랑 희건이랑 미서도 같이 마지막 단계에 들어왔을텐데 마지막까지 빠이팅했으면 좋겠다 ~~~ 누가 누굴 걱정하는 지 모르겠지만,, ^_^
위젯은 key라는 걸 가지고 있고, ID처럼 쓰인다는 개념을 이해해야 한다. 이유는 플러터가 코드 속 여러개의 위젯들을 식별하기 하기 위함이다. 일단 Flutter는 Java처럼 파일명과 클래스 이름이 반드시 일치해야한다는 깐깐함이 없어서 좋은 것 같다.
Bar부분의 명암도부터 배경색 및 글짜색을 넣어줬는데 니꼬는 폰트가 마음에 안 드는 모양이다. Google Fonts에서 폰트를 불러오자. 폰트를 고를 때 한 가지 팁이 있는데, 주로 쓰는 언어가 영어인지 한글인지를 잘 선택해서 해당 언어가 이쁜 폰트를 고르면 된다.
API에서 데이터를 불러와서 Fetch하는 목적의 코드 파일을 따로 만든다. 니꼬는 api_service.dart라고 이름 지었다.ApiService라는 class를 하나 생성하고 우리가 사용할 API의 url을 변수로 저장해둔다.
import 'package:http/http.dart' as http;
class ApiService {
final String baseUrl = "https://webtoon-crawler.nomadcoders.workers.dev";
final String today = "today";
}
이렇게! 그리고 이제부터는 JSON 데이터로 이 리스트를 받을 거다.
이 녀석들 !!! 일단 위 URL에 요청을 보내기 위해서는 http라는 패키지를 먼저 설치해야한다.
어디선가 본 적이 있는 것 같은 이 공식 패키지 보관소! Flutter나 Dart에 패키지가 필요하면 이 페이지를 이용하면 된다.
Node.js의 npm이나 Python의 PyPI랑 비슷한 개념이다!
http를 검색하면 찾을 수 있다. 눌러보면 호환되는 OS들부터 제작자 등등 정보들이 많이 뜬다. 그리고 command창에서 쓸 수 있는 버전과 Flutter 개발 중에 쓸 수 있는 버전을 모두 제공한다. 우리는 Flutter 개발 시에 쓰는 놈으로 '$flutter pub add http'를 가져온다.
복사한 text를 프로젝트에 대한 정보를 가지고 있는 pubspec.yaml에 넣어준다.
이전 강의에서 알려주지 않고 넘어간 것 같은, 온라인에서 폰트를 사용해서 설치하는 것도 여기서 만질 수 있는 것 같다. 이 주석을 잘 보면 내가 설치한 폰트의 파일 위치를 알려줘서 폰트를 화면상에 구현할 수 있다.
킹갓제네럴 GPT 선생님께서 알려주시는 YAML파일에 대한 설명이다. 조금 더 찾아봤더니 일단 프로젝트 파일들에 꼭 필요한 것은 아니다. 다만 파일의 형식을 나타내주고 용도나 형식을 빠르게 파악할 수 있어서 나중에 개발자 여러명이 개발할 때 서로 커뮤니케이션하는데 큰 도움을 주기 때문에 사용하는 습관을 들여두면 좋다고 한다. 그리고 프로젝트의 표준에 따라서 작성하는 양식의 차이가 있을 수 있다고도 한다. 신기방기..
그리고 니꼬쌤이 알려주는 꿀팁!
여기 버튼들 중에 가장 왼쪽에 있는 다운로드 버튼을 누르면 내가 필요로 하는 패키지들이 자동으로 다 설치가 된다. dependencies에 설치 할 패키지를 복붙만 하면 바로 설치가 된다니,,, WOW,, 아까 본 거 처럼 그냥 flutter pub add http라고 쳐도 된다고 하는데 지금까지는 이 방법만 알았어서 신기했다.
패키지를 설치했으면 이제 httml.뭐시기 메소드들을 다양하게 쓸 수 있다. 다시 api_service.dart에 돌아와서
코드를 추가해준다. 프러퍼티로 사용하는 두 개 이상의 변수들은 저렇게 /를 기준으로 구분지으면 된다. 그리고 중요한 개념, 동기 비동기의 차이가 등장한다. 우리는 Dart가 http.get(url);명령에 대해서 성공적으로 수행하기 전까지 다음 코드로 넘어가지 않고 요청이 돌아올 때까지 기다리게 해야한다.
그럴 땐 이렇게 await을 사용해주면 된다. awiat을 사용할 때는 유일한 규칙 하나가 있는데 asynchronous function(비동기 함수)내에서만 사용될 수 있다. 그래서 위 사진처럼 그냥 쓰면 에러가 발생하고,
이렇게 async를 적어줘야한다. 가끔 코딩을 하다보면 기다리게 해야할 때가 있다. 데이터가 돌아올 때까지 잠깐 멈춰야하는 때가 있다. 그럴 때 유용하게 사용할 수 있다.
void getTodaysToons() async {
final url = Uri.parse('$baseUrl/$today');
final response = await http.get(url);
if (response.statusCode == 200) {
print(response.body);
return;
}
throw Error();
}
}
그래서 이렇게 baseUrl과 today endpoint로 API URL을 만들고, API 서버에 요청을 보내는 함수 getTodaysToom() 비동기 함수를 만들었다.
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'];
}
print(webtoon)해주는 대신에 WebtoonModel.fromJson(webtoon)을 쓸 수 있다. 아무 에러 없이 작동한다.
API로부터 JSON을 넘겨주고 WebtoonModel의 title을 JSON의 title로 초기화시켜준다.
그리고
import 'package:toonflix/models/webtoon_model.dart';
class ApiService {
final String baseUrl = "https://webtoon-crawler.nomadcoders.workers.dev";
final String today = "today";
Future<List<WebtoonModel>> getTodaysToons() async {
List<WebtoonModel> webtoonInstances = [];
final url = Uri.parse('$baseUrl/$today');
final response = await http.get(url);
if (response.statusCode == 200) {
final List<dynamic> webtoons = jsonDecode(response.body);
for (var webtoon in webtoons) {
webtoonInstances.add(WebtoonModel.fromJson(webtoon));
}
return webtoonInstances;
}
throw Error();
}
이렇게 api_service.dart에 코드를 추가해준다. webtoonInstances.add를 만들고 그 안에다가 웹툰 클래스를 정의했다. 이해하기 쉽게 이 코드의 기능을 설명하자면 JSON으로 웹툰들을 받아올 때마다 그 웹툰들을 webtoonInstances 리스트에 추가해주는 거다! 그리고 아무것도 반환하는 게 아니라, webtoonInstances를 반환해준다. 그러니까 원래 아까대로 반환값이 없을 걸 생각하고 만들어뒀던 void타입의 함수를 List로 바꿔줘야 한다. 그리고 Future도 붙여줘야한다. 비동기 함수이기 때문에 !! 비동기 함수를 쓸 때 생각보다 고려해야하는 게 많다.
자 지금까지 API로 데이터를 fetch해서 JSON으로 된 데이터를 dart의 class로 바꿔주고, 모든 클래스를 리스트에 넣어서 그 리스트를 반환까지 했다. 이번에는 이제 클래스의 모든 method와 property를 static으로 바꿔줄 거다. 타입이 없는 함수는 static을 붙여주고, final로 선언된 변수들은 static const로 대체해준다.
그리고 위에 만들었던 Future 메서드를 HomeScreen에서 호출해줄 거다. 메인 스크린에다가 띄우고 싶은 데이터들이 들어있는 메서드니깐!
자 Future데이터를 불러오려고 하는데 여기에는 두 가지 방법이 있다.
먼저 리스트와 변수를 이렇게 만들어준다. 그리고initState()를 사용해서 super.initState를 호출하고 waitForWebToons이라는 함수를 호출해준다.
이렇게!! 자 그리고 이제 waitForWebToons()함수를 만들어주면 된다.
먼저 생각해야할 건 얘는 비동기 함수다. async써줘야하는!! 이유는 당연하다. http요청을 기다려줘야하는 함수이기 때문!
void waitForWebToons() async {
webtoons = await ApiService.getTodaysToons();
isLoading = false;
setState(() {});
}
이렇게 함수를 만들어주면 된다.
까먹지 말아야 할 것 !! 어떤 함수를 호출하고나면 Stateful Widget의 UI가 새로고침 됐었고 그럴 때마다 setState()도 호출해줘야 한다는 거 꼭 적어주자.
이게 Future의 데이터를 불러오는 기초적인 방법이다. 근데 디게 번거롭다. 함수로 새로 만들어주고 비동기를 고려해서 async도 써줘야하고 당연히 setState도 써줘야하고 그걸 또 initState에 써야한다. 좀 더 간단하게 사용할 수 있는 방법은 없을까!?
1번에서 했던 번거로운 과정을 하나하나 하는 것보다 더 편리하게 Future의 데이터를 가져오는 방법이 있다. 바로 Future Builder을 쓰면되는데 이건 5-5에서 계속 !!
얼마나 심플하고 유용한 지 보자. 지난 5-4에서 작성한 코드들을 모두 지워준다. 정리하기 전에 일단 이전 기초적이 방법과의 차이점을 말하자면 이 방법은 statefulwidget상태가 아니라 statelessWidget상태로 바로 데이터를 fetch해 올 수 있다.
HomeScreen({super.key}); 앞에 있던 const를 없애준다. const란 컴파일 전에 이미 그 값을알고 있다는 뜻인데 아래에 Future로 받아오게 구현하는 거라 미리 값을 알 수가 없으니까 당연한거다.
코드를 수정하고 콘솔을 보면 WebtoonModel 리스트의 Future가 찍히고 있는 것을 볼 수 있다. 리스트 자체가 아니라 리스트의 Future이 찍힌다는 게 중요한 거다. 무슨 말이냐면 결국 우리는 이 Future을 기다려야 한다. 그리고 여기서 이번 챕터의 핵심이 나오는데 정확히 이 Future을 위한 Widget이 있다는 것이고 이 놈이 바로 Future Builder이라는 녀석이다!
FutureBuilder를 적어놓고 커서를 올려서 도움말을 보자. 일단 이 녀석은 builder이라는 매개변수가 필요하다. 그리고 builder는 UI를 그려주는 함수다. 도움말에서도 볼 수 있듯이 initialData도 전달할 수 있다. 그리고!! future도 전달할 수 있다. 이게 지금 우리가 하려는 거다.
이렇게 해주면 FutureBuilder에게 webtoons를 기다려달라고 하는 거다.
여기서 await은 써주지 않아도 된다. futurebuilder가 알아서 해준다. 이제 매개변수들을 고려해서 코드를 만들고 가져올 snapshot에 데이터가 있는지 없는지 확인해주는 조건문도 달아주자.
body: FutureBuilder(
future: webtoons,
builder: (context, snapshot) {
if (snapshot.hasData) {
return const Text("There is data!");
}
return const Text('Loading....');
},
),
이렇게 부분을 완성할 수 있다. Future를 만들고, 그걸 FutureBuilder에 넣기만 하면 된다.
지난 챕터에서 FutureBuilder라는 Widget에 대해 알아봤다. 이번에는 List View인데 많은 양의 데이터를 연속적으로 보여주고 싶을 때 column이나 Row는 적절하지 않다. 그럴 떄 List View를 쓰는 거다. 바로 위 챕터 마지막 코드를 예시로, if조건 실행문 부분에 "There is data!"라는 문장을 출력하게 되어있는데 이게 긴 문장이나 많은 데이터였다면 이런식으로 사용하는 것은 지양해야 한다. 이 때 좋은 대안이 List View이다.
즉, List View는 여러항목을 나열하는데 최적화된 Widget이다. 이걸 써서 많은 문장들을 출력하면 overflow 에러가 나지도 않고 스크롤하면서 여러개의 데이터들을 쭈르륵 볼 수 있게 된다.
자, 그런데 이 List View에도 버전이 있다. 그냥 ListView()만 찍어서 사용하면 문제가 있다. 어떤 문제일까 !!
유튜브를 예로 들어보자. 유튜브를 내가 휴대폰으로 보고있는데