본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 30. · 15 Views
Provider 간 의존성 관리 완벽 가이드
Flutter에서 Riverpod Provider들이 서로 어떻게 연결되고 의존하는지 알아봅니다. 의존성 그래프부터 순환 참조 방지, 효율적인 구독 방법까지 실무에서 꼭 알아야 할 핵심 내용을 다룹니다.
목차
1. Provider 간 의존성이란
김개발 씨는 쇼핑몰 앱을 만들고 있었습니다. 사용자 정보를 관리하는 Provider와 장바구니를 관리하는 Provider를 각각 만들었는데, 장바구니에서 사용자의 회원 등급에 따라 할인율을 적용해야 하는 상황이 생겼습니다.
"어떻게 하면 한 Provider에서 다른 Provider의 데이터를 가져올 수 있을까요?"
Provider 간 의존성이란 하나의 Provider가 다른 Provider의 상태를 필요로 하는 관계를 말합니다. 마치 회사에서 부서 간에 협업하는 것과 같습니다.
영업팀이 재고 정보를 알아야 고객에게 정확한 납기를 안내할 수 있듯이, Provider도 다른 Provider의 데이터가 있어야 자신의 역할을 제대로 수행할 수 있습니다.
다음 코드를 살펴봅시다.
// 사용자 정보를 관리하는 Provider
final userProvider = StateProvider<User>((ref) {
return User(name: '김개발', grade: 'VIP');
});
// 사용자 등급에 따른 할인율 Provider
// userProvider에 의존합니다
final discountProvider = Provider<double>((ref) {
// 다른 Provider의 상태를 읽어옵니다
final user = ref.watch(userProvider);
// 등급에 따라 할인율 결정
return user.grade == 'VIP' ? 0.2 : 0.1;
});
김개발 씨는 입사 6개월 차 주니어 개발자입니다. Flutter로 쇼핑몰 앱을 열심히 만들고 있었는데, 점점 앱이 복잡해지면서 고민이 생겼습니다.
사용자 정보, 상품 목록, 장바구니, 주문 내역 등 관리해야 할 상태가 한둘이 아니었기 때문입니다. 처음에는 각각의 Provider를 독립적으로 만들었습니다.
그런데 문제가 생겼습니다. 장바구니의 총 금액을 계산할 때 사용자의 회원 등급에 따른 할인율을 적용해야 했던 것입니다.
"사용자 정보는 userProvider에 있고, 장바구니는 cartProvider에 있는데... 어떻게 연결하지?" 선배 개발자 박시니어 씨가 김개발 씨의 화면을 보더니 미소를 지었습니다.
"Provider 간 의존성을 활용하면 돼요." Provider 간 의존성이란 무엇일까요? 쉽게 말해, 한 Provider가 자신의 일을 하기 위해 다른 Provider의 정보를 필요로 하는 관계입니다.
이것을 회사 조직에 비유해 보겠습니다. 회사에는 여러 부서가 있습니다.
인사팀, 영업팀, 재고관리팀 등이 각자의 업무를 담당합니다. 그런데 영업팀이 고객에게 "이 상품 언제 받을 수 있나요?"라는 질문을 받으면 어떻게 할까요?
재고관리팀에 물어봐야 합니다. 이것이 바로 부서 간 의존성입니다.
Provider도 마찬가지입니다. discountProvider가 할인율을 계산하려면 사용자의 등급을 알아야 합니다.
사용자 등급은 userProvider가 관리하고 있으니, discountProvider는 userProvider에 의존하게 됩니다. 위의 코드를 살펴보겠습니다.
userProvider는 사용자 정보를 담고 있는 간단한 StateProvider입니다. 그리고 discountProvider 안에서 **ref.watch(userProvider)**를 호출합니다.
이 한 줄이 바로 의존성을 만드는 핵심입니다. ref.watch를 사용하면 두 가지 일이 일어납니다.
첫째, userProvider의 현재 값을 가져옵니다. 둘째, userProvider의 값이 변경되면 discountProvider도 자동으로 다시 계산됩니다.
만약 VIP 회원이 일반 회원으로 등급이 변경되면 어떻게 될까요? userProvider의 상태가 바뀌고, 이를 구독하고 있던 discountProvider도 자동으로 새로운 할인율을 계산합니다.
개발자가 직접 "야, 사용자 등급 바뀌었으니까 할인율도 다시 계산해!"라고 명령하지 않아도 됩니다. 이런 자동화된 의존성 관리가 Riverpod의 가장 큰 장점 중 하나입니다.
상태 간의 관계만 잘 정의해두면, 나머지는 프레임워크가 알아서 처리해 줍니다. 김개발 씨는 고개를 끄덕였습니다.
"아, 그러니까 Provider들이 서로 협력하는 구조를 만들 수 있는 거군요!"
실전 팁
💡 - Provider 간 의존성은 ref.watch로 쉽게 만들 수 있습니다
- 의존하는 Provider가 변경되면 자동으로 재계산됩니다
- 복잡한 비즈니스 로직도 작은 Provider들의 조합으로 표현할 수 있습니다
2. ref watch로 다른 Provider 구독
김개발 씨가 Provider 간 의존성의 개념을 이해하자 박시니어 씨가 말했습니다. "이제 본격적으로 ref.watch 사용법을 알아볼까요?
ref에는 watch 말고도 read도 있는데, 둘의 차이를 아는 게 정말 중요해요."
ref.watch는 다른 Provider를 구독하고, 그 값이 변경될 때마다 자동으로 반응하는 메서드입니다. 반면 ref.read는 현재 값만 한 번 읽어오고 변경을 감지하지 않습니다.
마치 뉴스 구독과 신문 가판대 구매의 차이와 같습니다. 구독하면 매일 새 뉴스가 배달되지만, 가판대에서 사면 그날 신문만 받게 됩니다.
다음 코드를 살펴봅시다.
final cartItemsProvider = StateProvider<List<CartItem>>((ref) {
return [];
});
// 총 가격 계산 - watch로 반응형 구독
final totalPriceProvider = Provider<int>((ref) {
// 장바구니 아이템이 변경되면 자동으로 재계산
final items = ref.watch(cartItemsProvider);
final discount = ref.watch(discountProvider);
final subtotal = items.fold(0, (sum, item) => sum + item.price);
return (subtotal * (1 - discount)).round();
});
// 위젯에서 사용하는 경우
class CartScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// 위젯에서도 watch로 구독
final total = ref.watch(totalPriceProvider);
return Text('총 금액: $total원');
}
}
박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다. "ref.watch와 ref.read의 차이를 신문에 비유해 볼게요." 신문을 구독하면 어떻게 될까요?
매일 아침 새 신문이 집 앞에 배달됩니다. 뉴스가 업데이트될 때마다 자동으로 최신 정보를 받아볼 수 있습니다.
이것이 바로 ref.watch입니다. 반면 신문 가판대에서 신문을 사면 어떨까요?
그 순간의 신문 한 부만 받게 됩니다. 다음 날 새 뉴스가 나와도 알 수 없습니다.
이것이 ref.read입니다. 김개발 씨가 물었습니다.
"그러면 항상 ref.watch를 쓰면 되는 거 아닌가요?" "좋은 질문이에요. 하지만 상황에 따라 적절히 사용해야 해요." ref.watch는 반응형입니다.
구독한 Provider의 값이 바뀌면 현재 Provider도 다시 계산됩니다. 위의 코드에서 totalPriceProvider를 보세요.
cartItemsProvider와 discountProvider를 모두 watch하고 있습니다. 장바구니에 아이템이 추가되면?
totalPriceProvider가 다시 계산됩니다. 사용자 등급이 바뀌어서 할인율이 변경되면?
역시 totalPriceProvider가 다시 계산됩니다. 이 모든 것이 자동으로 일어납니다.
위젯에서도 마찬가지입니다. ConsumerWidget의 build 메서드 안에서 ref.watch를 사용하면, 해당 Provider의 값이 변경될 때마다 위젯이 자동으로 다시 빌드됩니다.
그렇다면 ref.read는 언제 사용할까요? 버튼을 눌렀을 때 같은 일회성 액션에서 사용합니다.
예를 들어, "장바구니에 추가" 버튼을 누르면 현재 상품 정보를 한 번만 읽어서 장바구니에 넣으면 됩니다. 이때는 구독이 필요 없습니다.
박시니어 씨가 정리해 주었습니다. "쉽게 말해서, build 메서드나 Provider 내부에서는 watch를 쓰고, 콜백 함수나 이벤트 핸들러에서는 read를 쓰면 돼요." 주의할 점이 있습니다.
Provider 내부에서 ref.read를 사용하면 의존성이 추적되지 않습니다. 값이 바뀌어도 모르는 것입니다.
이것은 버그의 원인이 될 수 있으니 조심해야 합니다. 김개발 씨가 코드를 다시 살펴보며 말했습니다.
"ref.watch로 여러 Provider를 동시에 구독할 수도 있군요!" 맞습니다. 하나의 Provider에서 여러 개의 다른 Provider를 watch할 수 있습니다.
이렇게 하면 복잡한 비즈니스 로직도 작은 단위의 Provider들을 조합해서 표현할 수 있습니다.
실전 팁
💡 - Provider 내부와 build 메서드에서는 ref.watch를 사용하세요
- 버튼 클릭 같은 이벤트 핸들러에서는 ref.read를 사용하세요
- 하나의 Provider에서 여러 Provider를 watch할 수 있습니다
3. 의존성 그래프 시각화
김개발 씨의 쇼핑몰 앱이 점점 커지면서 Provider가 10개, 20개로 늘어났습니다. "이 Provider가 저 Provider를 참조하고, 또 저건 다른 걸 참조하고..." 머릿속이 점점 복잡해졌습니다.
박시니어 씨가 말했습니다. "의존성 그래프를 그려보면 한눈에 파악할 수 있어요."
의존성 그래프는 Provider들 사이의 참조 관계를 시각적으로 표현한 것입니다. 마치 가계도처럼, 어떤 Provider가 어떤 Provider에 의존하는지 한눈에 볼 수 있습니다.
Riverpod은 이런 의존성을 자동으로 관리해 주며, 개발자는 그래프를 통해 전체 구조를 파악하고 잠재적인 문제를 발견할 수 있습니다.
다음 코드를 살펴봅시다.
// 의존성 그래프 예시
// userProvider (최상위)
// |
// v
// discountProvider
// |
// v
// cartItemsProvider --> totalPriceProvider
// |
// v
// formattedPriceProvider
final userProvider = StateProvider<User>((ref) => User.guest());
final discountProvider = Provider<double>((ref) {
final user = ref.watch(userProvider);
return user.isVIP ? 0.2 : 0.0;
});
final totalPriceProvider = Provider<int>((ref) {
final items = ref.watch(cartItemsProvider);
final discount = ref.watch(discountProvider);
return calculateTotal(items, discount);
});
final formattedPriceProvider = Provider<String>((ref) {
final total = ref.watch(totalPriceProvider);
return '${total.toString().replaceAllMapped(RegExp(r'\B(?=(\d{3})+(?!\d))'), (m) => ',')}원';
});
박시니어 씨가 종이를 꺼내 그림을 그리기 시작했습니다. "Provider가 많아지면 머릿속으로만 관계를 파악하기 어려워요.
그래서 의존성 그래프가 필요합니다." 의존성 그래프는 가계도와 비슷합니다. 가계도를 보면 할아버지, 아버지, 나, 자녀로 이어지는 관계가 한눈에 보이죠?
의존성 그래프도 마찬가지로 Provider들의 관계를 보여줍니다. 위의 코드에서 주석으로 표현한 그래프를 살펴보겠습니다.
맨 위에 userProvider가 있습니다. 이것은 다른 어떤 Provider도 참조하지 않는 최상위 Provider입니다.
가계도로 치면 조상님 같은 존재입니다. discountProvider는 userProvider를 참조합니다.
화살표 방향에 주목하세요. 화살표는 "의존한다"는 뜻입니다.
discountProvider가 userProvider에 의존하니까, userProvider에서 discountProvider로 화살표가 향합니다. totalPriceProvider는 두 개의 Provider를 참조합니다.
cartItemsProvider와 discountProvider입니다. 이렇게 여러 부모를 가진 Provider도 있을 수 있습니다.
마지막으로 formattedPriceProvider는 totalPriceProvider만 참조합니다. 숫자로 된 가격을 "1,000원" 같은 형식의 문자열로 변환하는 역할입니다.
이 그래프에서 무엇을 알 수 있을까요? 첫째, 데이터의 흐름을 파악할 수 있습니다.
사용자 정보가 바뀌면 그 영향이 어디까지 퍼지는지 화살표를 따라가면 알 수 있습니다. userProvider가 바뀌면 discountProvider가 바뀌고, 그러면 totalPriceProvider가 바뀌고, 최종적으로 formattedPriceProvider까지 바뀝니다.
둘째, 병목 지점을 발견할 수 있습니다. 너무 많은 Provider가 하나의 Provider에 의존하고 있다면, 그 Provider가 바뀔 때마다 많은 재계산이 일어납니다.
이런 구조가 성능 문제를 일으킬 수 있습니다. 셋째, 순환 의존성을 발견할 수 있습니다.
A가 B를 참조하고, B가 다시 A를 참조하면 순환이 발생합니다. 이것은 다음 장에서 자세히 다루겠습니다.
김개발 씨가 물었습니다. "이 그래프를 자동으로 그려주는 도구가 있나요?" "네, Riverpod DevTools를 사용하면 실제 앱의 의존성 그래프를 시각화해서 볼 수 있어요.
개발 중에 매우 유용하죠." 그래프를 그리는 습관을 들이면 처음부터 깔끔한 구조를 설계할 수 있습니다. 복잡해지기 전에 미리 정리하는 것이 훨씬 효율적입니다.
실전 팁
💡 - Provider 관계를 종이나 도구로 시각화해 보세요
- 화살표 방향은 "의존한다"는 뜻입니다
- Riverpod DevTools로 실제 앱의 그래프를 확인할 수 있습니다
4. 순환 의존성 피하기
김개발 씨가 새로운 기능을 추가하다가 이상한 에러를 만났습니다. 앱이 실행되자마자 멈춰버린 것입니다.
에러 메시지를 보니 "Circular dependency detected"라고 적혀 있었습니다. 박시니어 씨가 화면을 보더니 한숨을 쉬었습니다.
"아, 순환 의존성이네요. 이건 절대 피해야 해요."
순환 의존성은 Provider A가 B를 참조하고, B가 다시 A를 참조하는 상황입니다. 마치 두 사람이 서로에게 "너 먼저 말해"라고 하며 영원히 기다리는 것과 같습니다.
Riverpod은 이런 상황을 감지하면 에러를 발생시킵니다. 순환 의존성은 설계 문제의 신호이므로, 구조를 재설계해야 합니다.
다음 코드를 살펴봅시다.
// 잘못된 예시 - 순환 의존성 발생!
final providerA = Provider<int>((ref) {
final b = ref.watch(providerB); // B를 참조
return b + 1;
});
final providerB = Provider<int>((ref) {
final a = ref.watch(providerA); // A를 참조 - 순환!
return a + 1;
});
// 올바른 해결 방법 - 공통 Provider 분리
final baseValueProvider = Provider<int>((ref) => 10);
final providerA = Provider<int>((ref) {
final base = ref.watch(baseValueProvider);
return base + 1;
});
final providerB = Provider<int>((ref) {
final base = ref.watch(baseValueProvider);
return base + 2;
});
김개발 씨의 얼굴이 당황으로 가득 찼습니다. "순환 의존성이요?
제가 뭘 잘못한 거죠?" 박시니어 씨가 천천히 설명을 시작했습니다. "순환 의존성은 생각보다 흔하게 발생하는 실수예요.
특히 코드가 복잡해질수록요." 순환 의존성이 무엇인지 일상적인 예로 설명해 보겠습니다. 두 친구가 식당에서 메뉴를 고르고 있습니다.
A가 말합니다. "넌 뭐 먹을 거야?
난 네가 고르는 거랑 다른 걸로 할게." B도 말합니다. "난 네가 고르는 거 보고 정할게." 이 두 사람은 영원히 주문할 수 없습니다.
서로가 서로의 선택을 기다리고 있기 때문입니다. 이것이 바로 순환 의존성입니다.
위의 잘못된 코드를 보세요. providerA는 providerB의 값을 기다립니다.
그런데 providerB도 providerA의 값을 기다립니다. 누가 먼저 계산되어야 할까요?
답이 없습니다. 그래서 Riverpod은 이런 상황을 감지하면 바로 에러를 발생시킵니다.
"그럼 어떻게 해결하나요?" 해결 방법은 공통된 근원을 찾는 것입니다. 위의 식당 예시에서, 만약 둘 중 한 명이 "난 그냥 파스타 먹을래"라고 먼저 결정하면 문제가 해결됩니다.
코드에서도 마찬가지입니다. A와 B가 서로를 참조하는 대신, 둘 다 참조할 수 있는 공통 Provider를 만듭니다.
위의 올바른 예시에서 baseValueProvider가 바로 그 역할을 합니다. 순환 의존성이 발생하는 전형적인 패턴이 있습니다.
첫째, 양방향 데이터 동기화를 하려 할 때입니다. "A가 바뀌면 B도 바꾸고, B가 바뀌면 A도 바꾸고..." 이런 설계는 순환의 늪에 빠지기 쉽습니다.
둘째, 서로 다른 화면의 상태를 직접 참조할 때입니다. 화면 A의 Provider가 화면 B의 Provider를 참조하고, 그 반대도 성립하면 순환이 발생합니다.
이런 상황을 피하려면 단방향 데이터 흐름을 유지해야 합니다. 데이터는 항상 위에서 아래로, 한 방향으로만 흐르게 설계하세요.
의존성 그래프에서 화살표가 한 방향으로만 가도록 하는 것입니다. 김개발 씨가 자신의 코드를 다시 살펴봤습니다.
"아, 제가 주문 Provider에서 결제 Provider를 참조하고, 결제 Provider에서 다시 주문 Provider를 참조했네요..." "맞아요. 이런 경우에는 주문과 결제의 공통 정보를 담는 새로운 Provider를 만들어서 둘 다 그걸 참조하게 하면 돼요." 순환 의존성 에러가 발생하면 당황하지 마세요.
오히려 설계를 다시 생각해볼 좋은 기회입니다. 대부분의 순환 의존성은 구조적인 문제를 알려주는 신호입니다.
실전 팁
💡 - 순환 의존성은 설계 문제의 신호입니다
- 공통 Provider를 분리하여 해결하세요
- 항상 단방향 데이터 흐름을 유지하세요
5. select로 부분 구독
김개발 씨가 앱의 성능을 측정해보니 예상보다 위젯이 너무 자주 다시 빌드되고 있었습니다. 사용자 정보 중 이름만 표시하는 위젯인데, 사용자의 포인트가 바뀔 때도 다시 빌드되는 것이었습니다.
"이름은 안 바뀌었는데 왜 다시 그리는 거죠?"
select는 Provider의 전체 상태 중 일부분만 구독할 수 있게 해주는 메서드입니다. 마치 신문 전체를 구독하는 대신 스포츠면만 구독하는 것과 같습니다.
관심 있는 부분만 변경되었을 때만 반응하므로 불필요한 재빌드를 줄여 성능을 최적화할 수 있습니다.
다음 코드를 살펴봅시다.
// 사용자 모델
class User {
final String name;
final int points;
final String email;
User({required this.name, required this.points, required this.email});
}
final userProvider = StateProvider<User>((ref) {
return User(name: '김개발', points: 1000, email: 'kim@dev.com');
});
// select로 이름만 구독 - 이름이 바뀔 때만 재빌드
class UserNameWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// points나 email이 바뀌어도 재빌드되지 않음
final name = ref.watch(userProvider.select((user) => user.name));
return Text('안녕하세요, $name님');
}
}
// select 없이 전체 구독 - 모든 변경에 재빌드
class UserWidgetWithoutSelect extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider); // 전체 구독
return Text('안녕하세요, ${user.name}님');
}
}
박시니어 씨가 성능 프로파일러 화면을 가리켰습니다. "여기 보세요.
이 위젯이 1초에 10번이나 다시 빌드되고 있어요." 김개발 씨가 의아해했습니다. "이상하네요.
이 위젯은 사용자 이름만 보여주는 건데..." "바로 그게 문제예요. 이름만 필요한데 사용자 정보 전체를 구독하고 있거든요." select의 개념을 이해하기 위해 신문 구독을 다시 생각해 봅시다.
신문을 구독하면 정치, 경제, 사회, 스포츠 등 모든 면이 옵니다. 하지만 당신이 스포츠에만 관심이 있다면?
다른 면이 바뀌어도 상관없지만, 전체 신문이 배달될 때마다 문 앞에 나가야 합니다. 만약 스포츠면만 구독할 수 있다면 어떨까요?
스포츠 기사가 업데이트될 때만 알림을 받으면 됩니다. 훨씬 효율적이겠죠?
ref.watch에 select를 붙이면 바로 이런 효과를 얻을 수 있습니다. 위의 코드를 비교해 보세요.
UserWidgetWithoutSelect는 ref.watch(userProvider)로 전체 User 객체를 구독합니다. 이 경우 name, points, email 중 어느 하나라도 바뀌면 위젯이 다시 빌드됩니다.
반면 UserNameWidget은 ref.watch(userProvider.select((user) => user.name))을 사용합니다. 이렇게 하면 오직 name 값이 바뀔 때만 위젯이 다시 빌드됩니다.
points가 1000에서 2000으로 바뀌어도 이 위젯은 꿈쩍도 하지 않습니다. select 안에 들어가는 함수를 **선택자(selector)**라고 부릅니다.
이 함수는 전체 상태에서 관심 있는 부분만 추출합니다. (user) => user.name은 "User 객체에서 name만 꺼내줘"라는 뜻입니다.
Riverpod은 선택자가 반환하는 값을 이전 값과 비교합니다. 값이 같으면 아무 일도 일어나지 않습니다.
값이 다를 때만 재빌드가 일어납니다. 이것이 왜 중요할까요?
복잡한 앱에서는 상태 변경이 매우 자주 일어납니다. 사용자가 스크롤 할 때마다, 데이터를 받아올 때마다, 애니메이션이 진행될 때마다 상태가 변경될 수 있습니다.
모든 변경에 모든 위젯이 반응하면 앱이 버벅거립니다. select를 적절히 사용하면 이런 성능 문제를 크게 줄일 수 있습니다.
김개발 씨가 물었습니다. "그럼 항상 select를 써야 하나요?" "꼭 그럴 필요는 없어요.
Provider의 상태가 단순하거나, 위젯이 정말로 전체 상태를 필요로 한다면 그냥 watch를 쓰면 돼요. select는 최적화가 필요한 곳에 선택적으로 사용하는 거예요."
실전 팁
💡 - 큰 객체에서 일부 속성만 필요할 때 select를 사용하세요
- select는 성능 최적화 도구이므로 필요한 곳에만 적용하세요
- 선택자 함수는 순수 함수여야 합니다
6. invalidate로 새로고침
김개발 씨가 새로운 요구사항을 받았습니다. "사용자가 새로고침 버튼을 누르면 서버에서 데이터를 다시 가져와야 해요." 지금까지 배운 watch와 read만으로는 이 기능을 구현하기 어려웠습니다.
박시니어 씨가 말했습니다. "이럴 때 invalidate를 사용하면 돼요."
ref.invalidate는 Provider의 상태를 무효화하고 다시 생성하도록 강제하는 메서드입니다. 마치 캐시를 삭제하고 새로운 데이터를 불러오는 것과 같습니다.
주로 사용자가 명시적으로 새로고침을 요청하거나, 특정 이벤트 후에 데이터를 갱신해야 할 때 사용합니다.
다음 코드를 살펴봅시다.
// 서버에서 사용자 데이터를 가져오는 Provider
final userDataProvider = FutureProvider<UserData>((ref) async {
final response = await apiClient.fetchUserData();
return UserData.fromJson(response);
});
// 새로고침 버튼이 있는 위젯
class ProfileScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userDataProvider);
return Column(
children: [
userAsync.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (e, s) => Text('에러: $e'),
),
ElevatedButton(
onPressed: () {
// Provider를 무효화하여 데이터 새로 불러오기
ref.invalidate(userDataProvider);
},
child: Text('새로고침'),
),
],
);
}
}
박시니어 씨가 설명을 시작했습니다. "지금까지 우리는 Provider가 자동으로 업데이트되는 경우만 봤어요.
하지만 때로는 개발자가 직접 업데이트를 명령해야 할 때가 있어요." invalidate를 이해하기 위해 카페의 커피 머신을 생각해 봅시다. 커피 머신은 물탱크의 물을 사용해서 커피를 내립니다.
보통은 물을 한 번 넣으면 여러 잔의 커피를 만들 수 있습니다. 그런데 어느 날 손님이 말합니다.
"물탱크 물이 오래된 것 같은데, 새 물로 바꿔주세요." 이때 바리스타는 물탱크의 물을 버리고 새 물을 채웁니다. 그리고 새 물로 커피를 내립니다.
ref.invalidate가 바로 이 동작을 합니다. "이 Provider의 상태를 버리고, 새로 만들어!"라고 명령하는 것입니다.
위의 코드에서 userDataProvider는 FutureProvider입니다. 앱이 시작될 때 서버에서 사용자 데이터를 한 번 가져옵니다.
하지만 사용자가 새로고침 버튼을 누르면 어떻게 될까요? 버튼의 onPressed에서 **ref.invalidate(userDataProvider)**를 호출합니다.
이 순간 Provider의 기존 상태는 사라지고, FutureProvider의 비동기 함수가 다시 실행됩니다. 서버에서 새로운 데이터를 가져오는 것입니다.
invalidate의 중요한 특징이 있습니다. 이 Provider에 의존하는 다른 Provider들도 함께 영향을 받습니다.
의존성 그래프를 기억하시나요? userDataProvider가 무효화되면, 이를 watch하고 있던 모든 Provider들도 다시 계산됩니다.
ref.refresh라는 비슷한 메서드도 있습니다. invalidate와 refresh의 차이는 무엇일까요?
invalidate는 Provider를 무효화만 하고, 실제로 다시 읽힐 때 재생성됩니다. refresh는 무효화하고 즉시 새 값을 반환합니다.
쉽게 말해, invalidate 후에는 누군가가 watch하거나 read할 때 새로 계산되고, refresh는 호출 즉시 새로 계산됩니다. 김개발 씨가 고개를 끄덕였습니다.
"그럼 새로고침 버튼에는 invalidate를 쓰고, 새 값을 바로 사용해야 하면 refresh를 쓰면 되겠네요?" "정확해요! 대부분의 새로고침 상황에서는 invalidate로 충분해요.
어차피 위젯이 watch하고 있으니까 알아서 새 값을 받게 되거든요." 주의할 점도 있습니다. invalidate를 너무 자주 호출하면 불필요한 네트워크 요청이 발생할 수 있습니다.
특히 FutureProvider의 경우, 매번 서버에 요청을 보내게 됩니다. 적절한 타이밍에만 사용하세요.
실전 팁
💡 - 새로고침 버튼이나 pull-to-refresh에 invalidate를 활용하세요
- invalidate는 lazy하게 동작하고, refresh는 즉시 동작합니다
- 네트워크 요청이 있는 Provider는 invalidate 남용에 주의하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
LangGraph Fault Tolerance 장애 복구 완벽 가이드
LangGraph 애플리케이션의 장애 복구 메커니즘을 실무 중심으로 배웁니다. Durable Execution부터 Graph Migrations까지 체크포인트 기반 복구 시스템을 스토리텔링으로 쉽게 이해할 수 있습니다.
LangGraph Time Travel 완벽 가이드
LangGraph의 Time Travel 기능으로 특정 시점으로 돌아가고, 상태를 포크하여 분기하고, 대안 경로를 탐색하는 방법을 배웁니다. 실무에서 디버깅과 실험에 활용하는 실전 가이드입니다.
LangGraph Persistence 완벽 가이드
LangGraph의 Persistence 기능을 활용하여 대화 상태를 저장하고 관리하는 방법을 배웁니다. Thread와 Checkpoint를 이해하고, 상태 조회 및 수정 방법을 실무 중심으로 학습합니다.
Riverpod 3.0 쇼핑 앱 종합 프로젝트 완벽 가이드
Flutter와 Riverpod 3.0을 활용한 실무 수준의 쇼핑 앱 개발 과정을 단계별로 학습합니다. 상품 목록, 장바구니, 주문, 인증, 검색 기능까지 모든 핵심 기능을 구현하며 상태 관리의 실전 노하우를 익힙니다.
Riverpod 3.0 Retry 자동 재시도 완벽 가이드
Riverpod 3.0에 새로 추가된 Retry 기능을 활용하여 네트워크 오류나 일시적인 실패 상황에서 자동으로 재시도하는 방법을 배웁니다. 초급 개발자도 쉽게 따라할 수 있도록 실무 예제와 함께 설명합니다.