White Cat's Paw

플러터/화면구현

[플러터] 상태관리가 되는 간단한 앱 -1 (쇼핑카트)

JungMayo 2025. 1. 20. 12:01

 

 

클릭을 통해서 추가,삭제하는 앱 만들어보기
main
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'home_screen.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        // 머터리얼 3 적용 방법
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
      ),
      home: HomeScreen(),
    ),
  );
}
home_screen(부모) 전체코드

 

import 'package:flutter/material.dart';
import 'package:flutter_statement_v01/_02/book_cart_page.dart';
import 'package:flutter_statement_v01/_02/book_list_page.dart';
import 'package:flutter_statement_v01/common.utils/logger.dart';


class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // 로컬 상태 : 하단에 활동화 된 탭 인덱스 번호
  int pageIndex = 0;
  // 상품 --> 책(String 타입으로 관리 예정 -> 이후 클래스로 묶기)

  // 공유 상태 카트에 담긴 책 정보(책 리스트 화면, 장바구니 화면에서 공유하는 데이터)
  // 배열로 변수 선언 + 초기화
  List<String> mySelectedBook = [];

  // 상태를 변경할 수 있는 메서드 만들기 (토글)
  void _toggleSaveStates(String book) {
    // 다시 화면을 그려라 요청 함수
    setState(() {
      if (mySelectedBook.contains(book)) {
        mySelectedBook.remove(book);
      } else {
        mySelectedBook.add(book);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    logger.d('홈스크린 메서드 호출됨');
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('텐코의 서재'),
          backgroundColor: Theme.of(context).colorScheme.tertiaryContainer,
        ),
        body: IndexedStack(
          // 반드시 추가해야 되는 속성
          // 페이지라는 인덱스로 로컬상태를 사용하고 있음
          index: pageIndex,
          children: [
            BookListPage(
              //_toggleSaveStates, 와 _toggleSaveStates()의 차이점은?
              // 1은 메서드 자체를 전달
              // 2는 return 값을 전달하는 것
              onToggleSaved: _toggleSaveStates,
              selectedBook: mySelectedBook,
            ),
            BookCartPage(
              selectedBook: mySelectedBook,
            ),
          ],
        ),
        bottomNavigationBar: BottomNavigationBar(
          // 필수 속성
          currentIndex: pageIndex,
          onTap: (index) {
            // 행위 .. 생략..
            setState(() {
              pageIndex = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.list),
              label: 'book_list',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.shopping_cart),
              label: 'cart',
            ),
          ],
        ),
      ),
    );
  }
}

 

  // 로컬 상태 : 하단에 활동화 된 탭 인덱스 번호
  int pageIndex = 0;
  // 상품 --> 책(String 타입으로 관리 예정 -> 이후 클래스로 묶기)

  // 공유 상태 카트에 담긴 책 정보(책 리스트 화면, 장바구니 화면에서 공유하는 데이터)
  // 배열로 변수 선언 + 초기화
  List<String> mySelectedBook = [];

  // 상태를 변경할 수 있는 메서드 만들기 (토글)
  void _toggleSaveStates(String book) {
    // 다시 화면을 그려라 요청 함수
    setState(() {
      if (mySelectedBook.contains(book)) {
        mySelectedBook.remove(book);
      } else {
        mySelectedBook.add(book);
      }
    });
  }

1. 인덱스를 나타낼 수 있는 변수를 하나 선언

 

2. List<String> mySelectedBook = []; 이라는 빈 배열을 하나 선언 및 초기화

--> 자식에서 토글을 상태를 변경했을 때 해당 배열에 값이 담길 수 있도록 하기 위함

 

3. _toggleSaveStates(String book) 이라는 메서드를 만들어서

자식에서 book을 매개변수로 전달했을 때 해당 메서드를 사용하여 book이 포함되어있으면 삭제하고 book이 포함되지 않았다면 추가하는 메서드

 

 children: [
            BookListPage(
              //_toggleSaveStates, 와 _toggleSaveStates()의 차이점은?
              // 1은 메서드 자체를 전달
              // 2는 return 값을 전달하는 것
              onToggleSaved: _toggleSaveStates,
              selectedBook: mySelectedBook,
            ),
            BookCartPage(
              selectedBook: mySelectedBook,
            ),
          ],

children 안에 page 자식들을 넣음

 

book_list_page 전체코드
import 'package:flutter/material.dart';
import 'package:flutter_statement_v01/common.utils/logger.dart';

class BookListPage extends StatelessWidget {
  //객체가 태어날때 부모와 연결 (String을 매개변수로)
  final Function(String) onToggleSaved;
  // 부모 위젯으로부터 넘겨 받은 장바구니 데이터(cart 목록)
  final List<String> selectedBook;
  BookListPage(
      {required this.onToggleSaved, required this.selectedBook, super.key});

  // 임시 데이터 - 교보문고의 볼 수 있는 책 목록
  final List<String> books = [
    '호모사피엔스',
    '다트입문',
    '홍길동전',
    '코드리펙토링',
    '전치사도감',
  ];

  @override
  Widget build(BuildContext context) {
    logger.d('데이터 확인 : ${selectedBook.toString()}');
    return ListView(
      children: books.map(
        // book <-- books의 0번째 인덱스는 호모사피엔스
        (book) {
          // 함수의 바디에는 식을 작성할 수 있다.
          // 사용자가 장바구니에 넣었는지 안넣었는지 어떻게 확인할 수 있을까

          // 부모가 관리하는 장바구니 데이터에 book이란 데이터가 있는가 없는가 체크하고 싶음
          final isSelectedBook = selectedBook.contains(book);

          return ListTile(
            leading: Container(
              width: 35,
              height: 35,
              decoration: BoxDecoration(
                color: Theme.of(context).secondaryHeaderColor,
                borderRadius: BorderRadius.circular(8.0),
                border: Border.all(color: Colors.black),
              ),
            ),
            title: Text(
              book,
              style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
            ),
            trailing: IconButton(
              onPressed: () {
                // 부모에게 콜백을 호출하는데 데이터도 함게 전달해야 함
                onToggleSaved(book);
              },
              icon: Icon(
                //isSelectedBook --> map 안의 지역 변수
                isSelectedBook ? Icons.remove_circle : Icons.add_circle,
                color: isSelectedBook ? Colors.red : Colors.green,
              ),
            ),
          );
        },
      ).toList(),
    );
  }
}
class BookListPage extends StatelessWidget {
  //객체가 태어날때 부모와 연결 (String을 매개변수로)
  final Function(String) onToggleSaved;
  // 부모 위젯으로부터 넘겨 받은 장바구니 데이터(cart 목록)
  final List<String> selectedBook;
  BookListPage(
      {required this.onToggleSaved, required this.selectedBook, super.key});

  // 임시 데이터 - 교보문고의 볼 수 있는 책 목록
  final List<String> books = [
    '호모사피엔스',
    '다트입문',
    '홍길동전',
    '코드리펙토링',
    '전치사도감',
  ];

1. 부모에서 정의했던 _toggleSaveStates를 사용하기 위해서 final Function(String) onToggleSaved;라는 익명함수를 선언

 

2. 부모에서 정의했던 List<String> mySelectedBook = [];을 사용하기 위한 변수 선언

 

Widget build(BuildContext context) {
  logger.d('데이터 확인 : ${selectedBook.toString()}');
  return ListView(
    children: books.map(
      // book <-- books의 0번째 인덱스는 호모사피엔스
      (book) {
        // 함수의 바디에는 식을 작성할 수 있다.
        // 사용자가 장바구니에 넣었는지 안넣었는지 어떻게 확인할 수 있을까

        // 부모가 관리하는 장바구니 데이터에 book이란 데이터가 있는가 없는가 체크하고 싶음
        final isSelectedBook = selectedBook.contains(book);

        return ListTile(
          leading: Container(
            width: 35,
            height: 35,
            decoration: BoxDecoration(
              color: Theme.of(context).secondaryHeaderColor,
              borderRadius: BorderRadius.circular(8.0),
              border: Border.all(color: Colors.black),
            ),
          ),
          title: Text(
            book,
            style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
          ),
          trailing: IconButton(
            onPressed: () {
              // 부모에게 콜백을 호출하는데 데이터도 함게 전달해야 함
              onToggleSaved(book);
            },
            icon: Icon(
              //isSelectedBook --> map 안의 지역 변수
              isSelectedBook ? Icons.remove_circle : Icons.add_circle,
              color: isSelectedBook ? Colors.red : Colors.green,
            ),
          ),
        );
      },
    ).toList(),
  );

1. 부모에서 index가 변경될 때 다시 build가 실행이 됨 -> return ListView가 보여짐

 

2. final isSelectedBook = selectedBook.contains(book);을 통해서 부모의 selectedBook의 배열에 book이 있는지 없는지 확인하는 bool 값 선언

 

3. Iconbutton에서 onPressed의 익명함수를 사용, 부모와 연결되어있는 onToggleSaved(book)을 사용하여 클릭했을 때 book이 있으면 remove, 없으면 add

 

4. isSelectedBook ? Icons.remove_circle : Icons.add_circle 의 삼항연산자를 사용하여 결과값을 나타낸다.

 

book_cart_page
import 'package:flutter/material.dart';

class BookCartPage extends StatelessWidget {
  // 사용자가 카드에 저장한 데이터만 화면에 뿌려주기
  final List<String> selectedBook;
  const BookCartPage({required this.selectedBook, super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
        children:
            selectedBook.map((book) => ListTile(title: Text(book))).toList());
  }
}