Flutter에서 InheritedWidget의 개선: Provider

Provider는 Flutter에서 상태 관리의 문제를 해결하기 위한 패키지로, 특히 Prop Drilling 문제를 효과적으로 해결할 수 있는 도구입니다. InheritedWidget을 보다 쉽게 사용할 수 있도록 래핑하여 상태 관리를 더 간단하고 효율적으로 만들어줍니다.

주요 개념

  • Provider: InheritedWidget을 래핑하여 더 쉽게 상태를 관리하고, 필요한 부분에서만 상태를 갱신할 수 있도록 도와줍니다.
  • ChangeNotifier: 상태가 변경될 때 이를 알리기 위한 클래스. notifyListeners() 메서드를 호출하여 상태 변경을 알립니다.
  • ChangeNotifierProvider: ChangeNotifier를 사용하는 Provider의 한 종류로, 상태 객체를 제공하고 관리합니다.
  • Consumer: 특정 Provider의 상태를 구독하여 변경될 때마다 빌드하는 위젯.

Provider 특징

  • Prop Drilling 문제 해결: 중간 위젯들에게 속성을 계속 전달해야 하는 문제를 해결할 수 있습니다.
  • Lazy Loading 지원: 필요할 때만 객체를 생성하여 성능을 최적화합니다.
  • 간단한 보일러플레이트: 새로운 클래스를 작성할 때 필요한 코드를 줄여줍니다.
  • Flutter DevTools 친화적: 상태를 시각화하고 디버깅을 쉽게 할 수 있습니다.
  • 상태를 별도 클래스로 분리: StatefulWidget보다 유지 관리가 용이합니다.
  • 작성해야 하는 코드 감소: InheritedWidget보다 코드량이 적습니다.
  • 선택적 갱신 가능: 원하는 위젯만 선택적으로 갱신할 수 있습니다.
  • Provider가 부모 위젯으로 등록되지 않은 경우, 자식 위젯에서 접근 시 런타임 에러 발생: 위젯 트리상에 등록할 때 의존성 순서가 중요합니다.

주요 속성 및 메서드

  • ChangeNotifierProvider: ChangeNotifier 객체를 제공하는 Provider입니다.
  • Consumer: Provider에서 제공하는 상태를 구독하고, 상태가 변경될 때마다 빌드합니다.
  • context.watch<T>(): Provider에서 상태를 구독하고 변경 시 위젯을 다시 빌드합니다.
  • context.read<T>(): 상태를 구독하지 않고 읽기만 합니다.
  • context.select<T, R>(R selector(T value)): 특정 속성만 구독하여 변경 시에만 빌드합니다.

실제 사용 예

기본 사용법

  1. ChangeNotifier 클래스 작성

    class Counter with ChangeNotifier {
      int _count = 0;
    
      int get count => _count;
    
      void increment() {
        _count++;
        notifyListeners();
      }
    }
    
  2. ChangeNotifierProvider로 상태 주입

    void main() {
      runApp(
        ChangeNotifierProvider(
          create: (context) => Counter(),
          child: MyApp(),
        ),
      );
    }
    
  3. Consumer를 사용하여 상태 접근 및 UI 업데이트

    class MyApp extends StatelessWidget {
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text('Provider Example'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text('You have pushed the button this many times:'),
                  Consumer<Counter>(
                    builder: (context, counter, child) {
                      return Text(
                        '${counter.count}',
                        style: Theme.of(context).textTheme.headline4,
                      );
                    },
                  ),
                ],
              ),
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                Provider.of<Counter>(context, listen: false).increment();
              },
              tooltip: 'Increment',
              child: Icon(Icons.add),
            ),
          ),
        );
      }
    }
    

Provider 접근 방법

  1. context.watch(): notifyListeners() 호출 시 위젯 갱신

    ProviderCart providerCart = context.watch<ProviderCart>();
    isInCart: providerCart.cartProductList.contains(product),
    onPressed: providerCart.onProductPressed,
    
  2. context.read(): notifyListeners() 호출 시 갱신하지 않음

    onPressed: context.read<ProviderCart>().onProductPressed,
    
  3. context.select(): 특정 속성이 변경된 경우에만 갱신

    List<Product> cartProductList = context.select<ProviderCart, List<Product>>(
      (providerCart) => providerCart.cartProductList,
    );
    
  4. Consumer: watch와 동일하나 특정 위젯만 갱신

    Consumer<Counter>(
      builder: (context, counter, child) {
        return Text(
          '${counter.count}',
          style: Theme.of(context).textTheme.headline4,
        );
      },
    );
    
  5. Selector: select와 동일하나 특정 위젯만 갱신

    Selector<ProviderBadge, int>(
      selector: (context, providerBadge) => providerBadge.counter,
      builder: (context, counter, child) => BottomBar(
        currentIndex: currentIndex,
        cartTotal: "$counter",
        onTap: (index) => setState(() {
          currentIndex = index;
        }),
      ),
    ),
    

Provider 제공자 종류

  1. ChangeNotifierProvider: 변경 사항을 알릴 필요가 있는 클래스

    ChangeNotifierProvider(
      create: (context) => ProviderCart(),
    )
    
  2. Provider: 변경 사항을 알릴 필요가 없는 클래스

    Provider(
      create: (context) => MyService(),
    )
    

고급 사용법

StatefulWidget 생애 주기 활용

  • initState()에서 Provider에 접근하려면 StatefulWidget의 속성에서 Provider를 생성해야 합니다.
  • ChangeNotifierProvider로 등록한 Provider는 해당 StatefulWidget이 위젯 트리에서 제거될 때 함께 dispose() 됩니다.
  • 예시:
    
    void initState() {
      super.initState();
      Provider.of<MyProvider>(context, listen: false).fetchData();
    }
    

Provider 상호작용

  • 특정 Provider의 상태가 변경될 때 다른 Provider의 상태도 함께 변경되어야 하는 경우
    class MyBadge with ChangeNotifier {
      MyBadge(this.myCart) {
        myCart.addListener(_update);
      }
    
      final MyCart myCart;
      int _count = 0;
    
      void _update() {
        _count = myCart.items.length;
        notifyListeners();
      }
    
      
      void dispose() {
        myCart.removeListener(_update);
        super.dispose();
      }
    }
    

관련자료

profile
flutter 개발자(진)

0개의 댓글