[Flutter] 데이터 모델링-①JSON 데이터 매핑

겨레·2024년 7월 15일
0
post-thumbnail

데이터 모델링을 해야 하는 이유는?

item['thumbUrl'] 이 맵... Map의 문제에서부터 온다.

오타가 발생한 존재하지 않는 값임에도 에러가 나지 않음. 이게 가장 큰 문제!
이를 막기 위해 class로 데이터를 모두 모델링 해준다.


① 새로운 폴더 및 파일 생성
restaurant 폴더에 model 폴더를 만들고 그 안에 restaurant_model.dart 만들기.
그리고 Model은 UI와 관련이 없기 때문에 Widget으로 만들지 않고, 그냥 class로 만들겠음!



② Model 생성

  • restaurant_model.dart 코드
// 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은 어떻게 쓸까? 위로 올라가서...

이렇게 수정해준다.
모델링을 잘 해두면, 인스턴화되어 자동 완성이 지원되고 오타를 방지할 수 있음.

  • restaurant_screen.dart 수정된 전체 코드
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); 
// 이렇게 하나만 넣고싶음!
 

이건 어떻게 할 수 있는지...
다음에 알아보자~




으.... 뭔가 뒤죽박죽인 느낌이다... 확실히 매핑하는 부분부터 헷갈리기 시작한다 ㅠㅠ

profile
호떡 신문지에서 개발자로 환생

0개의 댓글