Flutter

창고 이야기로 배우는 Riverpod 예제 코드

whs5758 2025. 8. 22. 18:58

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_blog/_test_code/shopping_app.dart';
import 'package:flutter_blog/main.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  //Flutter에 리버팟 사용
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: '리버팟 창고',
      home: ShoppingApp(),
    );
  }
}

 

providers.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 기본 창고를 설계 해 보자(변경 안되는 값들)
// 창고 메뉴얼 필요 없다
// 타입 안정성 설계 --> 제네릭 명시
final productsProvider = Provider<List<String>>((ref) {
  return ['사과', '바나나', '우유', '빵'];
});

// 2. 복합 창고를 만들기 전에 - 상테를 먼저 결정해야 한다(값 - 객체)
/// 사과나 바나나 등을 담을 수 있는 객체를 설계 해야 한다.
/// 2-1 상태를 먼저 설계 한다 (보통은 클래스 설계)

/// 상태 --> 객체 (불변 객체)
/// --> 접근 해서 상태 값을 변경 하면 오류가 발생할 수 있기 때문

class Cart {
  List<String> items;

  Cart({required this.items});
}

/// 2-2 창고 매뉴얼 설계도
class CartNotifier extends Notifier<Cart> {
  /// 중요 변수 - Notifier 클래스를 상속 받으면 state 변수가 기본적으로 있다. ///

  // 반드시 초기 상태가 어떤지 명시해 주어야 한다.
  /// 필수 ///
  @override
  Cart build() {
    return Cart(items: []);
  }

  // 상품을 추가하는 방법
  void add(String item) {
    // 불변 객체로 설계
    // 기존 리스트 + 새 상품 = 새로운 자료 구조 생성
    final newItems = [...state.items, item];
    state = Cart(items: newItems);
  }

  // 상품을 제거하는 방법
  void remove(String item) {
    // list 특정 조건을 확인하고 새로운 리스트를 만들어주는 메서드
    // ['사과', '바나나']
    // '바나나'
    final List<String> newItems =
        state.items.where((element) => element != item).toList();
    state = Cart(items: newItems);
  }
}

/// 2-3 실제 창고 생성 / 메뉴얼과 창고 데이터 명시
final cartProvider = NotifierProvider<CartNotifier, Cart>(() => CartNotifier());

 

shopping_app.dart
import 'package:flutter/material.dart';
import 'package:flutter_blog/_test_code/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 창고 시스템을 도입 - StatelessWidget 확장 --> ConsumerWidget 변겅
class ShoppingApp extends ConsumerWidget {
  const ShoppingApp({super.key});

  // WidgetRef ref - 통로
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 내가 만든 창고(productsProvider)에 접근해 --> 창고에서 관리하는 데이터 반환
    // ['사과', '바나나', '우유', '빵']
    final List<String> products = ref.read(productsProvider);
    final Cart cart = ref.watch(cartProvider);

    // ref.watch(provider) <-- 계속 구독
    // ref.read(provider) <-- 단 한번 요청
    return Scaffold(
      appBar: AppBar(
        title: Text('상점'),
        actions: [
          // 장바구니 개수 표시
          Center(child: Text('장바구니 ${cart.items.length} 개')),
          const SizedBox(width: 20),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: products.length,
              itemBuilder: (context, index) {
                final String product = products[index];
                return ListTile(
                  title: Text(product),
                  trailing: ElevatedButton(
                      onPressed: () {
                        // 상태 공유
                        // 창고에 데이터 추가 (기능)
                        // ref.read(cartProvider) --> 창고 데이터를 반환
                        // ref.read(cartProvider.notifier) --> 창고 메뉴얼에 접근
                        ref.read(cartProvider.notifier).add(product);
                      },
                      child: Text('담기')),
                );
              },
            ),
          ),
          Expanded(
            child: Column(
              children: [
                Text(
                  '장바구니',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                Expanded(
                  child: ListView.builder(
                    itemCount: cart.items.length,
                    itemBuilder: (context, index) {
                      final String item = cart.items[index];
                      return ListTile(
                        title: Text(item),
                        trailing: IconButton(
                          onPressed: () {
                            ref.read(cartProvider.notifier).remove(item);
                          },
                          icon: Icon(Icons.remove),
                        ),
                      );
                    },
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}