Provider — Remi Rousselet이 2019년 InheritedWidget의 래퍼로 만든 Flutter용 상태 관리 패키지입니다. Provider는 props drilling 없이 위젯 트리 아래로 데이터를 전달하는 문제를 해결합니다. 모든 위젯은 context.read<T>() 또는 context.watch<T>()를 통해 상태에 접근할 수 있습니다. pub.dev에 따르면 Provider는 25,000개 이상의 좋아요를 받은 Flutter에서 가장 인기 있는 상태 관리자입니다.
주요 내용
Provider — InheritedWidget 위에 구축된 Flutter의 상태 관리 및 의존성 주입을 위한 패키지입니다. Provider는 위젯 트리에 객체(상태, 서비스, 리포지토리)를 제공하고 데이터가 변경되면 자동으로 UI를 다시 빌드합니다. InheritedWidget을 직접 사용하는 것과 달리 Provider는 모든 보일러플레이트를 제거합니다. InheritedWidget의 하위 클래스를 작성하거나, 정적 of() 메서드를 설정하거나, 중첩을 관리할 필요가 없습니다.
Provider는 Google이 Flutter에서 공식적으로 권장하는 상태 관리 방법입니다(Flutter Team, 2019-2023). 이 패키지는 Flutter Ecosystem의 일부이며 Flutter 팀이 유지 관리합니다. 출시 당시 Provider는 전역 변수와 InheritedWidget의 대안으로 제안되었습니다. 생성자를 통해 전달하지 않고 어디서든 모든 객체에 접근할 수 있습니다.
Flutter Community Survey 2025에 따르면 Provider는 Flutter 애플리케이션의 72%에서 사용됩니다. 인기의 주요 이유는 최소한의 진입 장벽, 내장된 ChangeNotifier 지원, 다른 아키텍처(MVVM, BLoC)와의 호환성, 외부 의존성 없음입니다.
ChangeNotifier — Listener 패턴을 구현하는 Flutter의 내장 클래스입니다. ChangeNotifier는 notifyListeners()를 호출하여 구독자에게 변경 사항을 알립니다. Provider의 맥락에서 ChangeNotifier는 상태의 주요 클래스입니다. ChangeNotifier를 확장하는 클래스를 필드와 메서드와 함께 만들고 데이터 변경 후 notifyListeners()를 호출합니다.
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}notifyListeners 규칙: 데이터를 완전히 변경한 후에 호출합니다 — 메서드 중간이 아니라 끝에서. 메서드가 여러 변경을 수행하는 경우 각 변경 후가 아니라 모든 변경 후에 한 번 notifyListeners()를 호출합니다. 이렇게 하면 하나의 논리적 단계에서 여러 번 다시 그리는 것을 방지할 수 있습니다. 배치 업데이트의 경우 setState와 유사한 패턴과 함께 notifyListeners를 사용합니다.
ChangeNotifier의 대안: ValueNotifier — 단일 값용(기본 형식에 적합), StateNotifier — state_notifier 패키지에서(단독으로 거의 사용되지 않음). 대부분의 Provider 솔루션은 내장 지원과 단순함 때문에 ChangeNotifier를 사용합니다.
Consumer — ChangeNotifier를 구독하고 notifyListeners()가 호출될 때마다 다시 빌드하는 위젯입니다. Consumer는 context, model, child의 세 가지 매개변수가 있는 빌더 함수를 받습니다. Child — 모델에 의존하지 않고 Consumer가 다시 빌드하지 않는 위젯입니다. 이는 최적화입니다. Consumer에 정적 위젯(아이콘, 데이터 없는 텍스트)이 포함된 경우 child를 통해 전달되며 다시 생성되지 않습니다.
Consumer<CounterProvider>(
builder: (context, provider, child) => Column(
children: [
child!, // 다시 빌드되지 않음
Text('${provider.count}'),
ElevatedButton(
onPressed: () => provider.increment(),
child: Icon(Icons.add),
),
],
),
child: Text('카운터:'),
)context.watch — Provider를 구독하기 위한 BuildContext의 확장 메서드입니다. 모델을 반환하고 현재 위젯을 해당 변경 사항에 구독시킵니다. context.read — 구독 없이 접근(onPressed 핸들러, initState 및 dispose용). context.select — 다른 필드가 변경되어도 다시 빌드하지 않고 모델의 특정 필드를 구독합니다. Select는 10개 이상의 필드가 있는 복잡한 모델에 가장 효율적인 옵션입니다.
Consumer, watch 또는 select를 사용하는 경우: Consumer — 최적화를 위해 하위 위젯이 필요한 경우. watch — 간단한 읽기를 위해 build 메서드에서. select — 모델에 여러 필드가 있지만 위젯이 하나에만 의존하는 경우. Provider는 위젯이 파괴될 때 자동으로 구독을 취소하여 메모리 누수를 방지합니다.
MultiProvider — 중첩 없이 여러 Provider를 등록하기 위한 위젯입니다. Provider → Provider → Provider의 5단계 트리 대신 MultiProvider는 공급자 목록을 받습니다. 각 후속 Provider는 생성자를 통해 이전 Provider를 사용할 수 있습니다. MultiProvider는 애플리케이션의 루트 수준을 구성하는 표준 방법입니다.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ProxyProvider<AuthProvider, OrderProvider>(
update: (_, auth, __) => OrderProvider(auth.userId),
),
],
child: MaterialApp(home: HomePage()),
)ProxyProvider — 다른 Provider에 의존하는 Provider입니다. ProxyProvider는 다른 Provider에서 값을 가져와 자신의 객체에 전달합니다. 예를 들어 OrderProvider는 AuthProvider에 의존합니다(userId 필요). AuthProvider가 변경되면 ProxyProvider가 새 userId로 OrderProvider를 자동으로 다시 만듭니다. ChangeNotifierProxyProvider — ChangeNotifier용 ProxyProvider 버전입니다.
StreamProvider와 FutureProvider: StreamProvider는 Stream(Firebase, WebSocket)을 구독하고 새 이벤트마다 Consumer를 업데이트합니다. FutureProvider — 비동기 초기화용: Future를 실행하고 로딩을 표시한 다음 결과를 위젯에 전달합니다. 둘 다 수동 구독 관리 없이 일반적인 작업을 해결합니다.
Provider 테스트는 위젯을 테스트 값이 있는 MultiProvider로 감싸서 수행합니다. 테스트에 실제 API나 데이터베이스가 필요하지 않습니다. Provider는 모의 객체로 대체됩니다. provider 패키지는 테스트 격리를 위한 ProviderScope를 제공합니다. 각 테스트는 독립적으로 자체 Provider 트리를 만듭니다.
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments on button tap',
(tester) async {
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: CounterScreen(),
),
);
await tester.tap(find.byKey(Key('increment')));
await tester.pump();
expect(find.text('1'), findsOneWidget);
},
);
}MockProvider: API에 의존하는 Provider가 있는 위젯을 테스트하려면 스텁 하위 클래스를 만들거나 mockito / mocktail을 사용합니다. Provider는 특별한 모의 도구가 필요하지 않습니다. ChangeNotifier를 확장하는 모든 객체는 실제 서비스를 호출하지 않고 create를 통해 전달될 수 있습니다. 쉽게 교체할 수 있도록 인터페이스(추상 클래스)를 통해 Provider를 프로그래밍합니다.
Provider의 성능은 InheritedWidget을 기반으로 합니다. Provider가 변경되면 context.watch 또는 Consumer를 통해 구독된 모든 위젯이 다시 빌드됩니다. 불필요한 다시 그리기를 방지하려면 context.select(특정 필드 구독), Consumer의 child 매개변수, 정적 위젯에는 const를 사용합니다. Provider는 변경 사항을 구독하지 않은 브랜치를 다시 빌드하지 않습니다.
| 메서드 | 구독 | 다시 빌드 | 사용 |
|---|---|---|---|
| context.watch | 전체 모델 | 모든 변경 | 단순 위젯 |
| Consumer | 전체 모델 | 모든 변경 | child 최적화와 함께 |
| context.select | 특정 필드 | 필드 변경 시에만 | 복잡한 모델 |
| context.read | 없음 | 안 함 | 이벤트 핸들러 |
제한 사항: Provider는 이벤트 수준에서 비즈니스 로직 격리(BLoC와 같은)를 지원하지 않습니다. 모든 변경은 ChangeNotifier 메서드의 직접 호출을 통해 이루어지며, 통제되지 않은 변경 체인으로 이어질 수 있습니다. 복잡한 시나리오(여러 비동기 작업, 복잡한 유효성 검사)의 경우 Provider는 BLoC 및 Riverpod에 비해 부족합니다.
Provider에서 마이그레이션: Provider는 다른 패키지와 쉽게 결합할 수 있습니다. Riverpod으로 마이그레이션하려면 ChangeNotifierProvider.adaptive를 사용하세요. 기존 ChangeNotifier를 다시 작성하지 않고 Riverpod과 함께 사용할 수 있는 어댑터입니다. BLoC의 경우 — BlocProvider를 Provider 트리 내에 배치하여 ChangeNotifier를 점차 Bloc으로 대체할 수 있습니다.
자주 묻는 질문
Provider — ChangeNotifier와 함께 의존성 주입을 위한 InheritedWidget의 래퍼입니다. BLoC — 로직 격리를 위한 Event + Stream이 있는 아키텍처 패턴입니다. Provider는 배우기 쉽고 BLoC는 코드를 더 엄격하게 구조화합니다. Provider는 소규모 애플리케이션과 UI 상태에 적합하고 BLoC는 복잡한 비즈니스 로직에 적합합니다. Flutter Community 2025에 따르면 둘은 종종 같은 프로젝트에서 함께 사용됩니다.
ChangeNotifierProvider — ChangeNotifier 인스턴스를 위한 Provider 유형입니다. create를 통해 객체를 만들고 하위에 제공하며 notifyListeners가 호출되면 Consumer를 다시 빌드합니다. ChangeNotifierProvider는 트리에서 제거될 때 ChangeNotifier에서 자동으로 dispose를 호출합니다. 세 가지 생성 방법이 있습니다: ChangeNotifierProvider.value(기존 객체용), ChangeNotifierProvider(지연 생성용), ChangeNotifierProvider.create(명시적 지연 생성용).
context.watch 대신 context.select를 사용하세요. 선택한 필드가 변경될 때만 위젯이 다시 빌드됩니다. 큰 ChangeNotifier를 여러 개의 작은 것으로 나누세요(하나의 모델 — 하나의 책임). 정적 부분에는 Consumer child를 사용하세요. 목록에는 키와 함께 ListView.builder를 사용하세요. Provider DevTools(Flutter Inspector)는 어떤 위젯이 다시 빌드되고 있는지와 그 이유를 보여줍니다.
네. Provider(ChangeNotifier 없이) — 변경 불가능한 객체(리포지토리, API 클라이언트, 구성)를 주입하기 위한 것입니다. ValueListenableProvider — ValueNotifier용. StreamProvider — Stream(Firebase, WebSocket)용. FutureProvider — Future(시작 시 구성 로드)용. ProxyProvider — 다른 Provider에 의존하는 Provider용. ChangeNotifier는 UI 업데이트가 있는 변경 가능한 상태에만 필요합니다.
ProviderNotFoundException — 위젯 트리 상위에서 선언되지 않은 Provider를 가져오려고 할 때 발생하는 런타임 예외입니다. 일반적인 원인: Provider가 읽으려는 위젯보다 아래에 선언됨, Provider가 한 라우트에 선언되고 다른 라우트에서 읽힘, 타입 오타. 해결책: Provider를 트리 위로 올리거나 전역 의존성을 위해 MaterialApp 수준에서 MultiProvider를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.