[Flutter] 플러터 #16 - StateNotifierProvider
StateNotifierProvider를 사용하여 상태를 관리하는 방법을 알아보겠습니다.
StateNotifierProvider는 Riverpod 라이브러리에서 제공하는 상태 관리 프로바이더 중 하나로, 애플리케이션의 상태를 관리하는 데 사용됩니다. 이를 사용하면 상태를 저장하고 업데이트하는 데 사용되는 StateNotifier 클래스와 함께 사용되며, 앱의 상태를 변화시키고 이를 구독하는 위젯들에게 상태 변화를 알리는 역할을 수행합니다.
StateNotifierProvider의 역할은 다음과 같습니다:
- 상태의 제공: StateNotifierProvider는 애플리케이션의 상태를 제공합니다. 상태는 StateNotifier 클래스의 인스턴스를 통해 관리되며, 이를 프로바이더를 통해 위젯 트리에 주입하여 사용할 수 있습니다.
- 상태 업데이트: StateNotifierProvider는 StateNotifier의 메서드를 호출하여 상태를 업데이트할 수 있습니다. 이는 상태 변화를 일으키는 액션을 수행하는 로직이나 사용자 입력 등에 의해 발생할 수 있습니다. 업데이트된 상태는 자동으로 구독 중인 위젯들에게 알림이 전달됩니다.
- 상태 구독: StateNotifierProvider를 사용하는 위젯들은 제공된 상태를 구독할 수 있습니다. 이를 통해 상태 변화를 감지하고 UI를 업데이트할 수 있습니다. ConsumerWidget을 사용하거나 ref.watch를 통해 상태를 감시할 수 있습니다.
- 상태 의존성 관리: StateNotifierProvider는 위젯 트리에서 사용되는 다른 프로바이더들과 상태 간의 의존성을 관리합니다. 다른 프로바이더들이 StateNotifierProvider를 의존하고 있다면, 해당 프로바이더들은 상태 변화를 감지하여 필요에 따라 업데이트될 수 있습니다.
StateNotifierProvider를 사용하면 애플리케이션의 상태를 중앙에서 관리하고, 상태 변화에 따라 UI를 업데이트할 수 있습니다. 이를 통해 상태 관리 코드의 일관성과 가독성을 개선하고, 상태 변화를 추적하고 조작하기 쉬운 코드를 작성할 수 있습니다. 또한, 프로바이더의 의존성 관리 기능을 통해 다른 프로바이더들과의 상호작용을 용이하게 할 수 있습니다.
StateNotifierProvider를 사용하여 상태를 관리하는 방법을 코드로 보여드리겠습니다.
우선, 프로바이더에 사용되는 모델을 만들어줍니다.
class ShoppingItemModel {
// 이름
final String name;
// 갯수
final int quantity;
// 샀는지
final bool hasBought;
// 매운지
final bool isSpicy;
ShoppingItemModel({
required this.name,
required this.quantity,
required this.hasBought,
required this.isSpicy,
});
// 특정 값으로 변경할 때 사용된다. (copyWith)
ShoppingItemModel copyWith({
// 받을 값들을 모두 옵션으로 두기
String? name,
int? quantity,
bool? hasBought,
bool? isSpicy,
}) {
return ShoppingItemModel(
// 애초에 파라미터 또한 옵션 값으로 받았기에 전달받지 않은 값은 null로 표시된다.
// ?? 연산자를 통해서 구현 -> 받은 파라미터가 없어서 null이면 this.~~으로 값을 유지하고, 값을 전달 받았다면 그 값 대입
name: name ?? this.name,
quantity: quantity ?? this.quantity,
hasBought: hasBought ?? this.hasBought,
isSpicy: isSpicy ?? this.isSpicy,
);
}
}
이 코드에서는 ShoppingItemModel 클래스를 정의합니다. 이 클래스는 쇼핑 아이템을 나타내는 모델입니다. 이름, 갯수, 구매 여부, 매운지 여부 등의 속성을 가지고 있습니다. 또한, copyWith 메서드를 사용하여 특정 값으로 변경된 새로운 인스턴스를 반환할 수 있습니다.
다음은 StateNotifierProvider를 정의한 코드입니다.
final shoppingListProvider =
StateNotifierProvider<ShoppingListNotifier, List<ShoppingItemModel>>(
(ref) => ShoppingListNotifier());
// 이 밑에 거를 Provider로 만들어야함 사용할 수 있음 => StateNotifierProvider를 써야하고,
// 이것을 사용하기 위한 제네릭으로 ShoppingListNotifier와 사용할 타입을 넣어줘야함.
// -----------------------------------------------------------------------
// 순서도가 여기 먼저 봐야함 1번
// StateNotifier 그대로 상속받는 것 == ShoppingListNotifier가 StateNotifier의 기능을 그대로 받는 것
// StateNotifier를 상속할떄는 상태관리를 할 타입이 어떤 타입인지 지정해줘야함
class ShoppingListNotifier extends StateNotifier<List<ShoppingItemModel>> {
// super 값에다가 처음에 어떤 값으로 상태를 초기화할지 정해줘야 함, 무조건 위에서 지정한 타입의 값으로 넣어야함
// 뒤에 지정한 List<ShoppingItemModel>의 값을 super constructor에 넣게 되어있고, 그 값을 state 변수에 대입
// 그 말은 즉, 우리가 관리할 상태가 된다는 뜻
ShoppingListNotifier()
: super(
[
ShoppingItemModel(
name: '김치',
quantity: 3,
hasBought: false,
isSpicy: true,
),
ShoppingItemModel(
name: '라면',
quantity: 5,
hasBought: false,
isSpicy: true,
),
ShoppingItemModel(
name: '삼겹살',
quantity: 10,
hasBought: false,
isSpicy: false,
),
ShoppingItemModel(
name: '수박',
quantity: 2,
hasBought: false,
isSpicy: false,
),
ShoppingItemModel(
name: '카스테라',
quantity: 5,
hasBought: false,
isSpicy: false,
),
],
);
void toggleHasBought({required String name}) {
// 클래스의 값을 변경하기 위해서는 state를 불러와서 변경시켜줘야함
// state는 저기 위에 선언한 StateNotifier에 이미 있다.(따로 선언 X)
state = state
.map((e) => e.name == name
? ShoppingItemModel(
name: e.name,
quantity: e.quantity,
hasBought: !e.hasBought,
isSpicy: e.isSpicy)
: e)
.toList();
}
}
이 코드에서는 shoppingListProvider를 정의합니다. 이는 StateNotifierProvider로 상태를 관리하는 데 사용됩니다. ShoppingListNotifier는 StateNotifier 클래스를 상속하며, List<ShoppingItemModel> 타입의 상태를 관리합니다. 초기 상태로는 몇 가지 예시 쇼핑 아이템이 포함된 리스트를 가지고 있습니다.
ShoppingListNotifier 클래스는 toggleHasBought 메서드를 통해 상태를 업데이트합니다. 이 메서드는 특정 이름의 쇼핑 아이템의 구매 여부를 토글합니다. state를 가져와서 해당 이름의 아이템을 찾아 구매 여부를 변경한 후, 상태를 다시 할당하여 업데이트합니다.
마지막으로 StateNotifierProvider를 사용한 코드입니다.
class StateNotifierProviderScreen extends ConsumerWidget {
const StateNotifierProviderScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// super constructor로 선언되어있던 값들이 state에 바로 대입되어 그 김치 어쩌고가 바로 뜬다.
// // ref.watch or ref.read를 그냥 state에 대입하면 그 값만 대입이 된다.
final List<ShoppingItemModel> state = ref.watch(shoppingListProvider);
// final state = ref.watch(shoppingListProvider);
return DefaultLayout(
title: 'StateNotifierProvider',
body: ListView(
children: state
.map(
// 이건 그대로 이름만 가져오는 것
// (e) => Text(
// e.name,
// ),
(e) => CheckboxListTile(
title: Text(e.name),
value: e.hasBought,
onChanged: (value) {
// final List<ShoppingItemModel> state = ref.watch(shoppingListProvider);
// 이거를 가지고 오면 그 리스트 값만(다른 예제에서는 그 미리 선언한 타입의 값만) 가져오지만
// ~~.notifier의 형태로 가져올 경우 그 클래스 전체를 가지고 온다 (메소드도 같이 온다는 뜻)
ref.read(shoppingListProvider.notifier).toggleHasBought(
name: e.name,
);
},
))
.toList(),
),
);
}
}
이 코드는 ConsumerWidget을 상속하는 위젯으로, shoppingListProvider의 상태를 감시하고 해당 상태에 따라 UI를 렌더링합니다. ListView 위젯을 사용하여 쇼핑 아이템 리스트를 표시하고, 각 아이템은 CheckboxListTile로 표시됩니다. 사용자가 체크박스를 토글할 때마다 toggleHasBought 메서드가 호출되어 상태가 업데이트됩니다.
따라서, StateNotifierProvider를 사용하여 앱의 상태를 관리하면 상태를 공유하고 업데이트할 수 있으며, UI는 상태 변화에 따라 동적으로 업데이트됩니다. 사용자는 체크박스를 통해 쇼핑 아이템의 구매 여부를 변경할 수 있습니다. 이를 통해 상태 관리를 효과적으로 수행하고 앱의 동작을 제어할 수 있습니다.
코드를 실행 화면입니다.
실행화면본은 한장뿐이지만 체크박스의 상태에 변화를 준 뒤 뒤로가기 버튼을 누른다음 다시 본 화면에 들어오면 Reset되어있던 이전 상태와는 달리 체크박스의 상태 변하가 유지되어 있는 것을 알 수 있습니다.