[flutter] 장바구니 리스트 다중선택 및 해제

soonmuu·2023년 1월 17일
0

flutter

목록 보기
5/11
post-thumbnail

결과 이미지

구현한 기능

  • 전체선택/해제
  • 선택항목 해제(삭제)
  • 선택한 갯수
  • 선택한 누적 가격 출력

0. 임시 데이터 설정

장바구니 구성 임시 데이터를 만들어준다

  • 객체를 생성하고 json으로 변환하여 반환해준다
class cartItem {
  final int id;
  final String title;
  final int price;
  final String? thumbnail;

  cartItem({
    required this.price,
    this.title = '',
    required this.id,
    this.thumbnail = '',
  });

  Map toJson() {
    return {
      'id': id,
      'title': title,
      'price': price,
      'thumbnail': thumbnail,
    };
  }
}
  • 임시로 출력될 데이터를 만들어준다
 // 임시 데이터
  List<cartItem> sample_cart = [
    cartItem(
        id: 1,
        title: '상품명입니다',
        price: 1000,
        thumbnail:
            'https://cdn.pixabay.com/photo/2017/01/20/15/06/oranges-1995056__340.jpg'),
    cartItem(
        id: 2,
        title: '상품2',
        price: 2000,
        thumbnail:
            'https://cdn.pixabay.com/photo/2018/05/08/20/19/pomegranate-3383814__480.jpg'),
    cartItem(
        id: 3,
        title: '상품3',
        price: 1000,
        thumbnail:
            'https://cdn.pixabay.com/photo/2016/07/24/23/35/blackberries-1539540__340.jpg'),
  ];

1. provider 설치

상태관리를 위해 provider를 설치한다
https://pub.dev/packages/provider/install

 $ flutter pub add provider

설치가 되었다면 pubspec.yaml에 아래와 같이 버전이 명시된다

  provider: ^6.0.5

최상단 main.dart 파일에서 다른 상태관리도 함께 사용하기위해 MultiProvider로 사용했다.

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CartStore()),
      ],
  ...

provider의 더 많은 속성은 아래 링크에서 한국어로도 제공된다
https://github.com/rrousselGit/provider/blob/master/resources/translations/ko-KR/README.md

2. 선택된 장바구니값 설정 class 생성

장바구니 선택, 해제시 사용될 기능들을 구현해준다

class CartStore extends ChangeNotifier {
  bool _disposed = false; // 메모리 해제
  List<cartItem> data = []; // 생성될 데이터 변수 선언

  // 메모리 누수 방지
  @override
  void dispose() {
    _disposed = true;
    super.dispose();
  }

  // 메모리 해제가 아닐시 notifyListeners 호출
  @override
  notifyListeners() {
    if (!_disposed) {
      super.notifyListeners();
    }
  }

  // 장바구니 선택 추가
  void addItem(value) {
    // 장바구니 고유값 중복되지 않게 추가
    var index = data.indexWhere((item) => item.id == value.id);
    if (index == -1) {
      data.add(value);
      notifyListeners();
    }
  }

  // 장바구니 선택 해제
  void removeItem(value) {
    data.removeWhere((item) => item.id == value);
    notifyListeners();
  }

  // 장바구니 선택 전체 해제
  void clearItem() {
    data.clear();
    notifyListeners();
  }
}

선택된 데이터는 앞서 생성한 cartItem 객체로 받으며 id, price 값만 받는다

3. 장바구니 선택 추가와 해제

  1. 개별아이템 추가, 해제
  2. 전체 아이템 추가, 해제
  3. 선택 아이템 추가, 해제

3-1 개별 아이템

체크항목 변동을 위해 statefulWidget으로 생성한다
(편의상 장바구니 선택 기능에 필요한 변수만 표시)

class CartArticle extends StatefulWidget {
  const CartArticle({
    Key? key,
    required this.id,
    required this.checked,
    required this.price,
  }) : super(key: key);
  final int id;
  final bool checked;
  final int price;

  @override
  State<CartArticle> createState() => _CartArticleState();
}

class _CartArticleState extends State<CartArticle> {
  // 체크 항목 초기화
  bool cartChecked = false;
  
  @override
  Widget build(BuildContext context) {
  	// 외부에서 체크상태 변동시 반영
  	bool checked = widget.checked;
  ...

커스텀 ui 사용을 위해 checkbox 위젯을 사용하지 않고 InkWell 위젯을 사용하였다
2번에서 생성한 상태설정 기능을 사용할대에는 아래와 같이 사용하면된다
추가할때는 식별을 위한 id와 누적값 계산을 위한 price를 객체로 받았고 해제할때는 id 값일치여부로 저장된 값을 삭제했다

context.read<상태저장클래스>().[생성한 기능]

onTap: () {
	setState(() {
    	// 체크 여부 변동
		cartChecked = !cartChecked;
        
        // 체크 상태 업데이트
		if (cartChecked) {
			var newItem = cartItem(
				id: id,
				price: price,
			);
			context.read<CartStore>().addItem(newItem);
		} else {
			context.read<CartStore>().removeItem(id);
        }
	});                  
}, // onTap

3-2 전체 아이템

개별아이템과 비슷하다
다른점은 cartData (장바구니 전체 데이터) 를 받아와 전체선택을 반복문으로 실행한다.

   onTap: () {
		setState(() {
			checkAll = !checkAll;
			if (checkAll) {
				for (int i = 0; i < cartData.length; i++) {
					var newItem = cartItem(
                        id: cartData[i].id,
                        price: cartData[i].price,
                     );
                      context.read<CartStore>().addItem(newItem);
                 }
             } else {
                context.read<CartStore>().clearItem();
             }
		});
	},

3-3 선택 아이템

선택된 아이템을 삭제할때는 새로운 변수에 담은 후 반복문을 돌려야 정상적으로 실행된다.

한번 삭제될때마다 위젯이 업데이트 되기 때문에 배열을 모두 순환하지 못하고 반복문이 끝나버린다.

배열을 변수에 담을때는 a = b 와 같은 형식으로 선언하면 업데이트된 배열이 계속 새롭게 저장되기 때문에 List.from(data) 형태로 저장하여 사용한다

 btnSubmitFunc: () {
	// 선택 상태값 삭제
	var selected = List.from(data);
	for (var element in selected) {
		context.read<CartStore>().removeItem(element.id);
	}
  },

4. 선택 항목의 누적 가격

reduce 보다 조금더 유연한 fold를 사용하였다

 // 선택 상품 누적금액
    var total = data.fold(0, (int acc, cur) => cur.price + acc);

5. 선택항목 갯수 업데이트

선택된 배열을 가져와 해당 length를 읽어오면된다.

    // 선택된 상품 데이터
    var data = context.watch<CartStore>().data;
  • 상태값을 읽어오는 방법에는 아래와 같은 3가지 방법이 있다
    상태관리가 더 복잡해지면 watch 보다 select로 필요한 값이 업데이트 될때만 읽어올 수 있다

context.watch<T>() : 위젯이 T의 변화를 감지할 수 있도록 합니다.
context.read<T>() : T를 변화 감지 없이 return 합니다.
context.select<T, R>(R cb(T value)) : T의 일부 작은 영역에 대해서만 위젯이 변화를 감지할 수 있도록 합니다.

마치며

장바구니 추가, 최근검색어, 태그 다중선택 등 provider를 사용하여 비슷한 흐름으로 작업이 가능하다

profile
프론트엔드

0개의 댓글

관련 채용 정보