코드코도

[코독하구만 2] 4주차 - Flutter : Shop App 1 본문

Act/2021_여름_모각코_개인

[코독하구만 2] 4주차 - Flutter : Shop App 1

고도고도 고도고도 2021. 7. 28. 17:04
728x90

Udemy 강의가 어느덧 50퍼를 넘겼다. 중간에 어려운 부분도 몇 개 있었지만 이제 어느정도 혼자서 구축할 수 있을 것 같다. 사실 그때 그때 바로 정리하고 싶었는데 어쩌다보니 밀려서 오늘부터라도 바로바로 정리하는 시간을 가지려고 한다.

 

Shop 어플은 우선 GridView 로 상품을 보여주고 상품별로 title좋아요 , 장바구니담기 가 존재한다.

 

우선 이렇게 분리했다. models 에는 상품 에 대해 정의되어 있다.

 

아래가 product.dart 이다. 변수를 선언해주고 생성자까지 구현해주었다.

class Product {
  final String id;
  final String title;
  final String description;
  final int price;
  final String imageUrl;

  Product({
    required this.id,
    required this.title,
    required this.description,
    required this.price,
    required this.imageUrl,
  });
}

 

다음은 상품들을 UI 에 보여주기 위한 screen 이다.

초기에 더미 데이터를 생성시켜줬다.

import 'package:flutter/material.dart';
import '../widgets/product_item.dart';
import '../models/product.dart';

class ProductOverViewScreen extends StatelessWidget {
  final List<Product> loadedProducts = [
    Product(
      id: 'p1',
      title: 'Red Shirt',
      description: 'A red shirt - it is pretty red!',
      price: 29,
      imageUrl:
          'https://cdn.pixabay.com/photo/2016/10/02/22/17/red-t-shirt-1710578_1280.jpg',
    ),
    Product(
      id: 'p2',
      title: 'Trousers',
      description: 'A nice pair of trousers.',
      price: 59,
      imageUrl:
          'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Trousers%2C_dress_%28AM_1960.022-8%29.jpg/512px-Trousers%2C_dress_%28AM_1960.022-8%29.jpg',
    ),
    Product(
      id: 'p3',
      title: 'Yellow Scarf',
      description: 'Warm and cozy - exactly what you need for the winter.',
      price: 19,
      imageUrl:
          'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
    ),
    Product(
      id: 'p4',
      title: 'A Pan',
      description: 'Prepare any meal you want.',
      price: 49,
      imageUrl:
          'https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Cast-Iron-Pan.jpg/1024px-Cast-Iron-Pan.jpg',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("My Shop"),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10),
        itemCount: loadedProducts.length,
        itemBuilder: (ctx, index) => ProductItem(
          loadedProducts[index].id,
          loadedProducts[index].title,
          loadedProducts[index].imageUrl,
        ),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 1.5,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
      ),
    );
  }
}

이후 Widget 부분을 보면 bodyGridViewbuilder 로 정의해줬다. 다양한 방법이 있지만 builder 가 가장 편하다. builder 를 사용하기 위해선 itemCount itemBuilder gridDelegate 가 필요하다. itemCountGridView 로 그려낼 총 개수를 의미하고, itemBuilder 는 그려낼 아이템을 의미한다. gridDelegateGridView옵션 이다.

 

builder 에서 ProductItem 메소드를 호출하는 것을 볼 수 있는데 품목 에 해당하는 위젯 을 분리하기 위함이다. 그럼 product_item 을 살펴보자.

import 'package:flutter/material.dart';

class ProductItem extends StatelessWidget {
  final String id;
  final String title;
  final String imageUrl;

  ProductItem(this.id, this.title, this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return GridTile(
      child: Image.network(imageUrl, fit: BoxFit.cover),
      footer: GridTileBar(
        backgroundColor: Colors.black54,
        leading: IconButton(
          icon: Icon(Icons.favorite),
          onPressed: () {},
        ),
        title: Text(
          title,
          textAlign: TextAlign.center,
        ),
        trailing: IconButton(
          icon: Icon(
            Icons.shopping_cart,
          ),
          onPressed: () {},
        ),
      ),
    );
  }
}

넣어준 매개변수 id , title , imageUrl 을 이용하여 품목 을 그려낸다. 여기서 GridTile 로 구현하는데 LIstTile 과 동일하다. footerheader 가 존재한다. 말 그대로 footer 를 쓰면 바닥에 header 를 쓰면 천장에 구현된다. 예시를 보자. 첫 번째 사진이 header , 두 번째 사진이 footer 이다.

 

 

이후 child 요소들로 leading title trailing 이 존재하고 이들을 이용하여 배치를 할 수 있다. leadingfront , title 은 ㅇㅇ... trailingback 이라고 생각하면 쉽다. 그렇다면 현재 어플에서는 3가지 를 모두 사용했는데 이 중에 하나라도 사라진다면? 역시 예시를 보자.

 

첫 번째 사진은 leading 을 제거한 사진, 두 번째 사진은 title 을 제거한 사진, 세 번째 사진은 trailing 을 제거한 사진이다.

이렇게 구현이 된다.

 

GridTileBar 를 보니 subTitle 이라는 값이 존재한다. 살펴보자.

 

이후에 GridTile 로 구현한 상품 하단의 좋아요장바구니 버튼을 실제로 동작하도록 구현해봤다. 기존의 방식과 다른 방식으로 구현했는데 Provider 라는 새로운 개념을 도입했다. 사실 이해하는데 좀 어려웠다. 아직도 제대로 이해하지 못하고 있다고 봐도 무방하다. 일단 공식문서를 들고 왔다. 하지만 어려운게 현실...

 

https://pub.dev/packages/provider

 

provider | Flutter Package

A wrapper around InheritedWidget to make them easier to use and more reusable.

pub.dev

 

정리되어 있는 글이 있어서 첨부했다.

https://eunjin3786.tistory.com/255

 

[Flutter] Provider로 앱 상태 관리하기

아래 움짤처럼 여러 화면에서 상태를 공유해야할때 어떻게 해야하는지 State management 문서를 살펴보겠습니다. 1) 선언적으로 생각하기 우선 선언적으로 생각하라고 합니다. SwiftUI 처럼 state가 바

eunjin3786.tistory.com

 

간단하게 설명하자면 FlutterWidget 으로 구성되어 있는데 이게 어플을 제작하다보면 Widget 간의 Depth 가 기하급수적으로 증가하게 된다. main → class A, class B, class c... 뭐 상관 없을수도 있지만 만약에 서로 다른 부모를 갖는 (class A-1과 class C-3 이런 느낌?) 경우에 매번 build 를 다시해야한다. 이렇게 말하면 이해가 어려우니 예시를 들어보자.

 

상품에 좋아요장바구니 버튼이 존재하는데 좋아요 페이지와 장바구니 페이지가 있다고 가정해보자. 버튼이 클릭될 때마다 계속 build 가 발생한다. WidgetDepth 가 깊지 않으면 상관이 없지만 연속적으로 inherit 된다고 가정하면 메모리 누수 가 발생할 것이다. 이를 효율적으로 관리하는 것이 바로 provider 이다. 데이터 에 쉽게 접근하겠다는 것이 목표

 

products.dart 와 이를 사용하는 products_grid 를 살펴보자.

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

class Products with ChangeNotifier {
  List<Product> _items = [
    Product(
      id: 'p1',
      title: 'Red Shirt',
      description: 'A red shirt - it is pretty red!',
      price: 29,
      imageUrl:
          'https://cdn.pixabay.com/photo/2016/10/02/22/17/red-t-shirt-1710578_1280.jpg',
    ),
    Product(
      id: 'p2',
      title: 'Trousers',
      description: 'A nice pair of trousers.',
      price: 59,
      imageUrl:
          'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Trousers%2C_dress_%28AM_1960.022-8%29.jpg/512px-Trousers%2C_dress_%28AM_1960.022-8%29.jpg',
    ),
    Product(
      id: 'p3',
      title: 'Yellow Scarf',
      description: 'Warm and cozy - exactly what you need for the winter.',
      price: 19,
      imageUrl:
          'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
    ),
    Product(
      id: 'p4',
      title: 'A Pan',
      description: 'Prepare any meal you want.',
      price: 49,
      imageUrl:
          'https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Cast-Iron-Pan.jpg/1024px-Cast-Iron-Pan.jpg',
    ),
  ];

  var showFavoritesOnly = false;

  List<Product> get items {
    if (showFavoritesOnly) {
      return _items.where((productItem) => productItem.isFavorite).toList();
    }
    return [..._items];
  }

  Product findById(String id){
    return _items.firstWhere((element) => element.id == id);
  }

  void addProduct() {
    notifyListeners();
  }
}
import 'package:flutter/material.dart';
import '/providers/products.dart';
import '/widgets/product_item.dart';
import 'package:provider/provider.dart';

class ProductsGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final productsData = Provider.of<Products>(context);
    final products = productsData.items;

    return GridView.builder(
      padding: const EdgeInsets.all(10),
      itemCount: products.length,
      itemBuilder: (ctx, index) => ChangeNotifierProvider(
        create: (c) => products[index],
        child: ProductItem(),
      ),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        childAspectRatio: 1.5,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
      ),
    );
  }
}

products_grid 를 먼저 살펴보면 Provider.of<자료형>(context) 로 선언한 것을 볼 수 있다. 이것이 바로 Provider 로 관리할 변수이다. ChangeNotifierProviderProvider 로 관리하는 아이템에 상태 변화가 일어나면 setState 해주는 부분이다.

 

import 'package:flutter/material.dart';
import '/providers/product.dart';
import 'package:provider/provider.dart';
import '../screens/product_detail_screen.dart';

class ProductItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final product = Provider.of<Product>(context, listen : false);
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pushNamed(
              ProductDetailScreen.routeName,
              arguments: product.id,
            );
          },
          child: Image.network(product.imageUrl, fit: BoxFit.cover),
        ),
        footer: GridTileBar(
          backgroundColor: Colors.black54,
          leading: Consumer<Product>(
            builder: (ctx, product, child) => IconButton(
              icon: Icon(
                product.isFavorite ? Icons.favorite: Icons.favorite_border,
              ),
              onPressed: () {
                product.toggleFavoriteStatus();
              },
              color: Theme.of(context).accentColor,
            ),
          ),
          title: Text(
            product.title,
            textAlign: TextAlign.center,
          ),
          trailing: IconButton(
            icon: Icon(
              Icons.shopping_cart,
            ),
            onPressed: () {},
            color: Theme.of(context).accentColor,
          ),
        ),
      ),
    );
  }
}

ProductItem 을 살펴보면 역시 Provider.of<Product>(context, listen : false) 로 관리하고 있는 것을 볼 수 있다. 이후에 Consumer 도 등장하는데 Consumer 부분만 새로 build 하게된다.

 

설명이 많이 부족하긴 한데 나도 아직 100% 이해하고 있지는 않은 것 같다. 실제로 사용해보면서 감을 잡아야겠다.

 

소스코드는 아래 링크를 통해 접속하면 확인할 수 있다.

https://github.com/k906506/2021-Summer-Flutter-Study/tree/master/myfourthflutterapp

 

GitHub - k906506/2021-Summer-Flutter-Study: Studying hybrid app development using Dart and Flutter in summer vacation Mogakko

Studying hybrid app development using Dart and Flutter in summer vacation Mogakko 👾 - GitHub - k906506/2021-Summer-Flutter-Study: Studying hybrid app development using Dart and Flutter in summer va...

github.com

 

728x90
0 Comments
댓글쓰기 폼
Prev 1 2 3 4 5 6 7 Next