이 링크를 클릭하여 http라이브러리를 를 설치합니다.
설치 방법은 Instatlling 코너에 잘 나와있습니다!

get이라는 함수가 이름이 범용적이라 다른 라이브러리와 겹칠수가 있다. import 할 때 as키워드를 이용해 namespace를 만들어준다.
import 'package:http/http.dart' as http;
class ApiService {
final String baseUrl = "https://webtoon-crawler.nomadcoders.workers.dev";
final String today = "today";
void getTodaysToons() {
final url = Uri.parse('$baseUrl/$today');
http.get(url);
}
}
namespace는 변수, 함수, 구조체, 클래스 등 서로를 구분하기 위해 이름으로 사용되는 내부식별자에 유효범위를 제공하는 선언적 영역을 의미한다고 한다.
get 함수 위에 마우스를 올려보면 다음과 같은 사진이 나온다. 자세히 보면 반환형이 Future< Response >임을 알 수 있다.
Future은 미래에 받을 값의 타입을 알려준다고 한다. Response는 반환받을 응답에 대한 자료형이다.
보통 async와 await를 이용해 응답이 올때까지 기다리고, response라는 변수에 응답온 값을 담는다. 이때 응답이 이미 온 상태이기 때문에 response는 그냥 Response 타입이다.

다음 코드와 같이 jsonDecode를 이용해 String을 json형태로 변환 가능하다.
import 'dart:convert'; //import 시켜주고
import 'package:http/http.dart' as http;
class ApiService {
final String baseUrl = "https://webtoon-crawler.nomadcoders.workers.dev";
final String today = "today";
void getTodayToons() async{
final url = Uri.parse('$baseUrl/$today');
final response =await http.get(url);
if(response.statusCode ==200){
jsonDecode(response.body); //String을 넣어준다.
return;
}
throw Error();
}
}
개인적으로 제일 궁금했던 부분이다. 아래 코드와 같이 Named Constructor를 이용해 json을 인자로 받아 title, thumb,id를 초기화해준다.
class WebtoonModel {
final String title, id, thumb;
WebtoonModel.fromJson(Map<String, dynamic> json)
: title = json['title'],
thumb = json['thumb'],
id = json['id'];
}
사용 시엔 아래와 같이 jsonDecode결과를 List에 담아 for문을 이용해 사용하면 된다.
class ApiService {
final String baseUrl = "https://webtoon-crawler.nomadcoders.workers.dev";
final String today = "today";
void getTodayToons() async{
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){
WebtoonModel.fromJson(webtoon);
}
return;
}
throw Error();
}
}
HomeScreen에서 state초기화를 위해 initState함수를 override 했습니다.initState함수가 뭔지 알아보기 위해 공식문서를 참고했습니다.
Called when this object is inserted into the tree.
The framework will call this method exactly once for each State object it creates.
react로 치면 useEffect(()=>{},[])같은 느낌인가봅니다.
import 'package:flutter/material.dart';
import 'package:toonflix/models/webtoon.dart';
import 'package:toonflix/services/api_service.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<WebtoonModel> webtoons =[];
bool isLoading =true;
void waitForWebToons() async{
webtoons = await ApiService().getTodayToons(); //data를 api로 받아옴
isLoading = false; //로딩 false
setState((){}); //상태변경
}
void initState(){//초기화 코드 작성
super.initState();
waitForWebToons();
}
Widget build(BuildContext context) {
print(webtoons);
print(isLoading);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 2,
backgroundColor: Colors.white,
foregroundColor: Colors.green,
title: const Text("오늘의 웹툰",
style: TextStyle(
fontSize: 24,
)),
),
);
}
}
data를 api로 받아옴
로딩 false
상태변경
초기화 코드 작성
데이터를 fetch하는건 항상 이런 식이다.<----너무 명언이다.
저런 생각을 하는 순간 더 나은방법을 고민하기 때문이다. 중복이 많아지고, 실수를 할 수 있는 코드는 지양해야겠다 싶다가도 귀찮아서 별 생각없이 코드를 짤 때가 많은데 앞으로는 더 나은 방향을 조사해보는 시도를 해야겠다.

builder는 UI를 그려주는 함수이다. initial Data도 전달할 수 있다.
snapshot은 Future의 상태이다. 위의 클래스를 StatelessWidget으로 바꾸고,body에 FutureBuilder를 이용하여 코드를 작성하면 로딩 false 코드 추가, setState를 하지 않고도 같은 효과를 낼 수 있다.
class HomeScreen extends StatelessWidget {
HomeScreen({Key? key}) : super(key: key);
Future<List<WebtoonModel>> webtoons = ApiService().getTodayToons();
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 2,
backgroundColor: Colors.white,
foregroundColor: Colors.green,
title: const Text("오늘의 웹툰",
style: TextStyle(
fontSize: 24,
)),
),
body:FutureBuilder(
future: webtoons,
builder: (context,snapshot){
if(snapshot.hasData){
return Text("There is data");
}
return const Center(
child:CircularProgressIndicator(),
);
},
),
);
}
}
위에 보면 Center의 child로 CircularProgressIndicator()를 리턴했는데, 이건 로딩창이다. 와..진짜 너무 간결하고 좋다.
ListView.Builder를 이용하면 android의 리사이클러뷰처럼 user가 볼 수 있는 리스트의 범위만 로딩하고 보여준다. 스크롤이 어어엄청 긴 경우를 생각해보면, 유저가 스크롤 하지 않은 부분까지 미리 로딩해둔다면 메모리가 많이 사용될 것이다. 그럴때는 유저가 보는 부분만 로딩하여 보여주는것이 좋다.
코드 설명은 주석으로 적어두었다.
//scaffold의 body
body:FutureBuilder(
future: webtoons,
builder: (context,snapshot){
if(snapshot.hasData){
return ListView.builder(//user가 볼 수 있는 리스트만 구현
scrollDirection: Axis.horizontal,//스크롤의 방향
itemCount: snapshot.data!.length,//전체크기(requird가 아닌데 왜넣어주지?)
itemBuilder: (context,index){//required.
print(index);//보이는 리스트의 범위가 호출된다.(index가 0~10까지 호출됨)
//스크롤을 뒤로 이동하면 또 그 곳에서 보이는 리스트의 범위들만 호출된다. (필요할때 로딩된다.)
var webtoon = snapshot.data![index];
return Text(webtoon.title);
},
);

seperated를 이용하면 item사이에 구분자를 넣어줄 수 있다. 이때, seperatorBuilder를 꼭 추가해줘야 한다.
body:FutureBuilder(
future: webtoons,
builder: (context,snapshot){
if(snapshot.hasData){
return ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data!.length,
itemBuilder: (context,index){
print(index);
var webtoon = snapshot.data![index];
return Text(webtoon.title);
},
separatorBuilder: (context,index)=>const SizedBox(width:20),
);
}
return const Center(
child:CircularProgressIndicator(),
);
},
),

궁금해서 itemCount라인을 주석처리를 해봤다.(극단적)
그랬더니 다음과 같은 메시지가 나왔다.
itembuilder callback은 0~아이템 개수까지 호출된다는 말 같다. 그래서 itemCount가 required인듯하다.
