데이터 모델링을 해야 하는 이유는?
item['thumbUrl'] 이 맵... Map의 문제에서부터 온다.
오타가 발생한 존재하지 않는 값임에도 에러가 나지 않음. 이게 가장 큰 문제!
이를 막기 위해 class로 데이터를 모두 모델링 해준다.
① 새로운 폴더 및 파일 생성
restaurant 폴더에 model 폴더를 만들고 그 안에 restaurant_model.dart 만들기.
그리고 Model은 UI와 관련이 없기 때문에 Widget으로 만들지 않고, 그냥 class로 만들겠음!
② Model 생성
// priceRange는 3가지 값이 있음.
// 따로 enum으로 만들어주겠음.
enum RestaurantPriceRange { expensive, medium, cheap }
class RestaurantModel {
final String id;
final String name;
final String thumbUrl;
final List<String> tags;
final RestaurantPriceRange priceRange;
final double ratings;
final int ratingsCount;
final int deliveryFee;
final int deliveryTime;
// ★ 모델을 생성할 때(인스턴스화 할 때) 무조건 이 값들은 파라미터에 넣어줘야 함!
// 위에 값들을 인스턴스화 할 때 파라미터로 값을
// 꼭 넣어줘야 되게 하기 위해서 required this. 해주기
RestaurantModel({
required this.id,
required this.name,
required this.thumbUrl,
required this.tags,
required this.priceRange,
required this.ratings,
required this.ratingsCount,
required this.deliveryFee,
required this.deliveryTime,
});
}
③ item을 한 번 더 파싱
restaurant_screen.dart으로 가서 item을 일반 Map으로 받지 않고,
item을 한 번 더 파싱해주기.
그럼 이 pItem은 어떻게 쓸까? 위로 올라가서...
이렇게 수정해준다.
모델링을 잘 해두면, 인스턴화되어 자동 완성이 지원되고 오타를 방지할 수 있음.
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_actual/common/const/data.dart';
import 'package:flutter_actual/restaurant/component/restaurant_card.dart';
import 'package:flutter_actual/restaurant/restaurant_model.dart';
class RestaurantScreen extends StatelessWidget {
const RestaurantScreen({super.key});
Future<List> paginateRestaurant() async {
final dio = Dio();
final accessToken = await storage.read(key: ACCESS_TOKEN_KEY);
final resp = await dio.get(
'http://$ip/restaurant',
options: Options(
headers: {
'authorization': 'Bearer $accessToken',
},
),
);
// return resp.data를 하면 실제 바디 가져올 수 있음!
// 그런데 내가 가져오고 싶은 값은?
// data라는 키 안에 있는 값들만 반환할 거임!! -> 그래야 List 값을 가져올 수 있기에...
return resp.data['data'];
}
Widget build(BuildContext context) {
return Container(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
// <List>를 사용해 어떤 값이 들어오는지 확인
child: FutureBuilder<List>(
future: paginateRestaurant(),
builder: (context, AsyncSnapshot<List> snapshot) {
// 만약 snapshot에 data가 없으면
if (!snapshot.hasData) {
return Container();
}
// data가 있으면 ListView를 리턴
return ListView.separated(
// itemCount에는 몇 개의 아이템을 넣을 건지
itemCount: snapshot.data!.length,
// itemBuilder는 index를 받아서 각 아이템별로 렌더링
itemBuilder: (_, index) {
// 아이템 저장
final item = snapshot.data![index];
// 파싱
final pItem = RestaurantModel(
id: item['id'],
name: item['name'],
thumbUrl: 'http://$ip${item['thumbUrl']}',
tags: List<String>.from(item['tags']),
// priceRange는 인원값을 맵핑
// values들을 하나씩 맵핑하면서 firstWhere 값을 찾는 것!
// 어떤 값이냐면... high, medium, cheap 중 똑같은 값!
// 똑같은 item의 ['priceRange']과 똑같은 값을 찾는 것.
priceRange: RestaurantPriceRange.values
.firstWhere((e) => e.name == item['priceRange']),
ratings: item['ratings'],
ratingsCount: item['ratingsCount'],
deliveryFee: item['deliveryFee'],
deliveryTime: item['deliveryTime']);
return RestaurantCard(
// network => url에서부터 data를 가져오기
image: Image.network(
pItem.thumbUrl,
fit: BoxFit.cover,
),
// image: Image.asset('asset/img/food/ddeok_bok_gi.jpg',
// fit: BoxFit.cover),
name: pItem.name,
tags: pItem.tags,
ratingsCount: pItem.ratingsCount,
deliveryTime: pItem.deliveryTime,
deliveryFee: pItem.deliveryFee,
ratings: pItem.ratings,
);
},
// separatorBuilder => 각 아이템 사이사이에 들어가는 거를 빌드
separatorBuilder: (_, index) {
return const SizedBox(
height: 16,
);
},
);
},
),
),
),
);
}
}
재실행하면 정상적으로 랜더링되는 걸 확인할 수 있다!
(+) restaurant_model에 값들을 넣을 수 있는 건 좋지만,
외부에서 매번 변환할 때마다 이 변환하는 코드를 계속 작성하는 것은 비효율적...
final pItem2 = RestaurantModel(
id: id,
name: name,
thumbUrl: thumbUrl,
tags: tags,
priceRange: priceRange,
ratings: ratings,
ratingsCount: ratingsCount,
deliveryFee: deliveryFee,
deliveryTime: deliveryTime)
// 이렇게 다 적지 않고
final pItem2 = RestaurantModel(item);
// 이렇게 하나만 넣고싶음!
이건 어떻게 할 수 있는지...
다음에 알아보자~
으.... 뭔가 뒤죽박죽인 느낌이다... 확실히 매핑하는 부분부터 헷갈리기 시작한다 ㅠㅠ