9Cells

State

Flutter에서 생성된 화면의 변경이 필요하면 state(상태)를 바꾸고 다시 화면을 생성하는 방식으로 만들어야 합니다. 다른 프레임워크처럼 변경이 필요한 부분을 수정하는 방식이 아니라 변경이 필요한 부분을 다시 만드는 방식입니다.

이 방식은 화면 변경을 위한 단 하나의 방법만을 제공하기 때문에 코드를 관리하기 편리합니다. 다른 프레임워크처럼 수정하는 방식이라면 변경 이벤트를 받아 변경하려는 화면을 얻고 수정 명령을 내려야 합니다. Flutter의 방식은 최초 화면을 생성하는 코드와 변경 시 다시 생성하는 코드가 같습니다. 화면 생성 코드 내에서 상태에 따라 분기되어 적절한 화면이 만들어지는 형태이므로 변경이 필요하면 상태를 바꾸고 다시 생성을 하면 됩니다. 변경을 위한 코드를 해석해가며 찾지 않아도 생성 코드와 같은 위치에서 수정 코드를 찾을 수 있기 때문에 작동방식을 이해하는 것이 간단합니다.

Provider 패키지를 사용하여 state 관리를 쉽게 할 수 있습니다. Provider를 설치하려면 pubspec.yaml 파일의 dependencies:provider: ^3.0.0 추가하고 프로젝트 디렉토리에서 flutter pub get 명령을 실행합니다. Provider는 ChangeNotifier, ChangeNotifierProvider, Consumer 세 부분을 이해해야 합니다.

ChangeNotifier

상태를 저장하는 객체는 ChangeNotifier를 상속하여 만들어야 합니다. 상태가 변경되면 notifyListeners()를 호출하여 상태가 변경되었음을 알립니다.

class ProductModel extends ChangeNotifier {
  final List<Product> _products = [];

  void add(Product prd) {
    _products.add(prd);
    notifyListeners(); // notifyListeners()
  }
}

ChangeNotifierProvider

ChangeNotifierProvider는 앞서 만든 ChangeNotifier의 인스턴스를 자손에게 제공합니다. ChangeNotifierProvider은 위젯을 상속받아 구현되었기 때문에 위젯 계층 구조에 집어넣을 수 있습니다. ChangeNotifierProvider의 자손으로 추가된 위젯들은 ChangeNotifier를 제공받을 수 있습니다.

같은 위치에서 여러개의 ChangeNotifierProvider를 제공하고 싶다면 다음 예제처럼 MultiProvider를 사용할 수 있습니다.

void main() {
  runApp(
    // MultiProvider
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => ProductModel()),
      ],
      child: Shopping(),
    ),
  );
}

Consumer

상태를 사용하는 위젯에서 Consumer를 사용하여 ChangeNotifier를 얻을 수 있습니다. Consumer는 builder 펑션을 필요로 합니다. builder는 ChangeNotifier의 notifyListeners()가 호출되어 변경이 생기면 호출됩니다.

    child: Consumer<ProductModel>(
      builder: (context, products, child) {
        return Text('Product');
      },
    ),

Model 접근

상태를 변경하기 위해 model에 접근이 필요할 수 있습니다. 예를 들어, 카트 비우기 버튼을 클릭하여 카트에 담긴 모든 상품을 지운다고 가정해봅시다. 아래와 같은 코드로 모델에 접근하여 카트 리스트를 비울 수 있습니다. removeAll() 메소드 안에서 notifyListeners()를 호출하면 변경을 알리게 되고 변경을 대기하던 Consumer는 화면을 다시 생성하면서 카트 아이템 위젯을 만들지 않으므로 카트 화면은 비워지게 됩니다.

Provider.of<ProductModel>(context).removeAll();