본문 바로가기
PROJECT

커피 앱 만들기(1) - 메뉴 및 선택 총 금액 표시하기

by Nuridal_class 2024. 11. 20.
728x90
728x90

이번에는 저희 어머니가 커피 만드시는 일을 하시는데 단골들만 오는 곳이라 구두로 주문을 받고

수기로 작성하더라구요. 그런 불편함을 조금이라도 덜어드리기 위해서 앱을 만들어보겠습니다.


 Coffe APP 만들기 (1)

먼저 이번 프로젝트의 버전부터 설명드리겠습니다.
🏷️ 먼저 개발에 사용되었던 버전 정보들입니다.
FVM version: 3.2.1
Flutter version: 3.19.3
Dart version: 3.3.1
GetX versio: 4.6.6

Git version: 2.47.0.windows.2
Java version: OpenJDK 11 2018-09-25

 

1.  파일구조

먼저 파일 구조는 아래와 같습니다.
이번 프로젝트는 MVVM 패턴으로 개발을 진행해보겠습니다.

lib/
├── controllers/
│   └── menu_controller.dart
├── models/
│   └── menu_item.dart
├── routes/
│   ├── route_name.dart
│   └── route_page.dart
├── screens/
│   └── home.dart
├── utils/
│   └── main_color.dart
├── widgets/
│   ├── appbar_widget.dart
│   ├── category_menu_list.dart
│   ├── menu_item_card.dart
│   └── total_price_bar.dart
└── main.dart

 

2. 컴포넌트 요약

 2.1 menu_controller.dart

👨‍💻컨트롤러는 MVVM 패턴에서 ViewModel 역할을 수행하며, 비즈니스 로직을 처리합니다.
- GetX를 사용한 반응형 상태 관리
- 메뉴 아이템의 CRUD 작업 처리
- 실시간 총액 계산
- 카테고리별 메뉴 관리 (커피, 에이드, 스무디, 차)
✍상태관리

final RxList<MenuItem> menuItems = <MenuItem>[].obs;  // 메뉴 아이템 리스트
final RxDouble totalPrice = 0.0.obs;

✍초기화
onInit(): 컨트롤러가 생성될 때 자동으로 메뉴 아이템을 로드
loadMenuItems(): 초기 메뉴 데이터를 추가
2천원 메뉴: 아메리카노, 아이스티, 에이드, 스무디
3천원 메뉴: 차, 라떼류

✍수량관리
void incrementQuantity(int index) {
  menuItems[index].quantity.value++;
  calculateTotal();
}

void decrementQuantity(int index) {
  if (menuItems[index].quantity.value > 0) {
    menuItems[index].quantity.value--;
    calculateTotal();
  }
}

✍총액계산
void calculateTotal() {
  totalPrice.value = menuItems.fold(
      0.0, (sum, item) => sum + (item.price * item.quantity.value));
}

 

 2.2 menu_item.dart

👨‍💻모델은 메뉴 아이템의 데이터 구조를 정의하며, MVVM 패턴에서 Model 역할을 수행한다.
👨‍💻불변성 보장
- 모든 필드가 final로 선언되어 객체 생성 후 변경 불가
👨‍💻반응형 상태 관리
- quantity를 RxInt로 선언하여 GetX의 반응형 상태 관리 활용
- 수량 변경 시 자동으로 UI 업데이트
👨‍💻필수 파라미터
-  name, price, category는 필수 입력
- quantity는 기본값 0
👨‍💻초기화 리스트
- quantity를 obs로 변환하여 반응형으로 만듦
✍클래스 정의
class MenuItem {
  final String name;      // 메뉴 이름
  final int price;        // 메뉴 가격
  final String category;  // 메뉴 카테고리
  final RxInt quantity;   // 주문 수량 (반응형)
}

✍생성자
MenuItem({
  required this.name,
  required this.price,
  required this.category,
  int quantity = 0,
}) : quantity = quantity.obs;

 

 2.3 home.dart

👨‍💻메인화면 구성
👨‍💻탭바를 통한 카테고리 관리
👨‍💻메뉴 리스트 표시
class Home extends StatefulWidget {
  TabController _tabController;
  // 탭바와 메뉴 리스트 표시
}

 

 2.4 main.dart

👨‍💻상태 관리
- GetX를 사용한 상태 관리
- TabController를 통한 탭 상태 관리
👨‍💻UI/UX
- 탭 기반 네비게이션
- 반응형 디자인
👨‍💻구조
- MVVM 패턴의 View 역할
- 위젯 컴포넌트화
- 재사용 가능한 구조
✍상태관리 초기화
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  final CafeMenuController menuController = Get.find<CafeMenuController>();
  late TabController _tabController;
}

✍생명주기관리
@override
void initState() {
  super.initState();
  _tabController = TabController(length: 4, vsync: this);
}

@override
void dispose() {
  _tabController.dispose();
  super.dispose();
}

✍UI 구성
- AppBar 
  타이틀: 'COFFEE MENU'
  서브타이틀: 'Choose your favorite drink'
  Google Fonts 적용
  하단 탭바 (커피, 에이드, 스무디, 차)
- Body
  TabBarView를 사용한 카테고리별 메뉴 표시
  각 탭에 CategoryMenuList 위젯 배치
  하단에 TotalPriceBar 위젯 배치

 

 2.5 route_name, page.dart

👨‍💻route_name.dart에서 경로 상수 정의
👨‍💻route_page.dart에서 실제 라우트 매핑
👨‍💻main.dart의 GetMaterialApp에서 라우트 설정 사용
✍Name
class RouteName {
  static const home = '/home';  // 홈 화면의 라우트 경로
}

✍Page
class RoutePage {
  static final page = [
    GetPage(
      name: RouteName.home,      // 라우트 이름
      page: () => const Home(),  // 연결될 화면
      transition: Transition.noTransition,  // 화면 전환 효과 없음
    ),
  ];
}

 

 2.6 main_color, appbar_widget.dart

👨‍💻앱의 디자인 시스템을 구성
✍Color
class MainColor {
  Color mainColor() {
    return const Color.fromARGB(255, 0, 217, 255);  // 하늘색
  }
}

✍Appbar
PreferredSizeWidget basicAppBar(
    BuildContext context, GlobalKey<ScaffoldState> scaffoldKey) {
  return AppBar(
    backgroundColor: Colors.white,
    elevation: 0.5,
    title: Column(
      children: [
        Text('COFFEE MENU', style: GoogleFonts.playfairDisplay(...)),
        Text('Choose your favorite drink', style: GoogleFonts.lato(...)),
      ],
    ),
    centerTitle: true,
  );
}

 

 2.7 category_menu_list.dart

👨‍💻카테고리별 메뉴 목록을 표시하는 재사용 가능한 컴포넌트이다.
👨‍💻반응형 상태 관리
- Obx로 감싸서 자동 UI 업데이트
- GetX의 반응형 상태 관리 활용
👨‍💻리스트 뷰
ListView.builder로 효율적인 리스트 구현
- 동적으로 아이템 수 계산
👨‍💻아이템 표시
- MenuItemCard 위젯으로 각 메뉴 아이템 표시
- 정확한 인덱스 참조를 위해 indexOf 사용
✍클래스 정의
class CategoryMenuList extends StatelessWidget {
  final String category;
  final CafeMenuController menuController;
}

✍카테고리 메뉴 필터링
List<MenuItem> getMenuByCategory() {
  return menuController.menuItems
      .where((item) => item.category == category)
      .toList();
}

✍UI 구성
Widget build(BuildContext context) {
  return Obx(
    () => ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: getMenuByCategory().length,
      itemBuilder: (context, index) {
        final item = getMenuByCategory()[index];
        return MenuItemCard(...);
      },
    ),
  );
}

 

 

 2.8 menu_item_card.dart

👨‍💻디자인
- Card 위젯으로 메뉴 아이템 표시
- ListTile로 정보 구조화
👨‍💻상호작용
- 증가/감소 버튼으로 수량 조절
- controller를 통한 상태 관리
- 반응형 수량 표시 (Obx 사용)
👨‍💻레이아웃
- trailing 영역에 수량 조절 UI
✍클래스 정의
class MenuItemCard extends StatelessWidget {
  final MenuItem item;          // 메뉴 아이템 정보
  final int index;             // 메뉴 아이템 인덱스
  final CafeMenuController controller;  // 메뉴 컨트롤러
}

✍UI 구성
Card(
  margin: const EdgeInsets.only(bottom: 8),
  child: ListTile(
    title: Text(item.name),           // 메뉴 이름
    subtitle: Text('${item.price}원'), // 메뉴 가격
    trailing: Container(...)          // 수량 조절 버튼
  ),
)

✍수량조절 UI
Row(
  children: [
    IconButton(icon: Icons.remove, ...),  // 감소 버튼
    Obx(() => Text('${item.quantity.value}')),  // 현재 수량
    IconButton(icon: Icons.add, ...),     // 증가 버튼
  ],
)

 

 2.9 total_price_bar.dart

👨‍💻상태 관리
- GetX의 Obx로 반응형 가격 표시
- controller를 통한 총액 계산
- 파란색으로 강조된 가격 표시
✍클래스 정의
class TotalPriceBar extends StatelessWidget {
  final CafeMenuController controller;  // 메뉴 컨트롤러
}

✍컨테이너 스타일링
Container(
  padding: const EdgeInsets.all(16),
  decoration: BoxDecoration(
    color: Colors.white,
    boxShadow: [
      BoxShadow(
        color: Colors.grey.withOpacity(0.2),
        spreadRadius: 1,
        blurRadius: 5,
        offset: const Offset(0, -3),  // 위쪽 그림자
      ),
    ],
  ),
)

✍가격표시 UI
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: [
    Text('총 주문금액:'),  // 레이블
    Obx(() => Text('${controller.totalPrice.value}원'))  // 동적 가격
  ],
)

 

3. 실행화면


이번에는 카페 메뉴 결제 어플을 만들어 보려고 하는데요

마지막에는 토스를 사용해서 결제하는 방법까지 만들어보겠습니다!

728x90
300x250