ConsumerStatefulWidget
A StatefulWidget that provides direct access to Riverpod through ConsumerState and WidgetRef.
What is it?
ConsumerStatefulWidget is Riverpod’s version of Flutter’s StatefulWidget.
It combines:
- Flutter’s stateful lifecycle (
initState,dispose, etc.) - Riverpod’s reactive provider system via
ref
This allows you to use both local mutable state and global reactive state in the same widget.
Why does it exist?
ConsumerWidget is great for stateless reactive UI, but it cannot handle:
- lifecycle hooks (
initState,dispose) - controllers (TextEditingController, AnimationController)
- subscriptions
- imperative logic tied to widget lifecycle
Before ConsumerStatefulWidget, you had to manually mix Riverpod with StatefulWidget and pass around ref using Consumer.
This created boilerplate and inconsistency.
ConsumerStatefulWidget solves this by:
- giving direct access to
refinside state class - integrating Riverpod into Flutter lifecycle
- reducing boilerplate
- keeping reactive + imperative logic together cleanly
Syntax
Basic Usage
class CounterPage extends ConsumerStatefulWidget {
const CounterPage({super.key});
@override
ConsumerState<CounterPage> createState() => _CounterPageState();
}
Explanation:
- Extends
ConsumerStatefulWidgetinstead ofStatefulWidget - Creates a
ConsumerStateinstead ofState
State Class with ref
class _CounterPageState extends ConsumerState<CounterPage> {
@override
void initState() {
super.initState();
ref.read(counterProvider);
}
@override
Widget build(BuildContext context) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
Explanation:
refis available in lifecycle methods and buildref.watch()triggers rebuildsref.read()is used for one-time access
Using Controllers
class SearchPage extends ConsumerStatefulWidget {
const SearchPage({super.key});
@override
ConsumerState<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends ConsumerState<SearchPage> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final results = ref.watch(searchProvider(controller.text));
return Column(
children: [
TextField(controller: controller),
Expanded(child: Text(results.toString())),
],
);
}
}
Explanation:
- Local controller is managed via lifecycle methods
- Riverpod reacts to input changes via provider
- Combines imperative and reactive logic
Mental Model
Think of ConsumerStatefulWidget as:
A StatefulWidget with built-in access to Riverpod.
Without Riverpod:
StatefulWidget → local state only
With Riverpod:
StatefulWidget + ref → local + global reactive state
It bridges Flutter lifecycle and Riverpod state system.
Examples
Simple Counter
class CounterPage extends ConsumerStatefulWidget {
const CounterPage({super.key});
@override
ConsumerState<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends ConsumerState<CounterPage> {
@override
Widget build(BuildContext context) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('Count: $count'),
),
);
}
}
Explanation:
- Uses Riverpod state inside StatefulWidget
- Rebuilds automatically when counter changes
Real-World Example
class ProfilePage extends ConsumerStatefulWidget {
const ProfilePage({super.key});
@override
ConsumerState<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends ConsumerState<ProfilePage> {
@override
void initState() {
super.initState();
ref.read(profileProvider.notifier).loadProfile();
}
@override
Widget build(BuildContext context) {
final profile = ref.watch(profileProvider);
return profile.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
);
}
}
Explanation:
initStatetriggers initial data loadbuildreacts to provider updates- Clean separation of lifecycle and UI logic
Form Handling Example
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final emailController = TextEditingController();
@override
void dispose() {
emailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isLoading = ref.watch(authProvider).isLoading;
return Column(
children: [
TextField(controller: emailController),
ElevatedButton(
onPressed: isLoading
? null
: () {
ref
.read(authProvider.notifier)
.login(emailController.text);
},
child: Text(isLoading ? 'Loading...' : 'Login'),
),
],
);
}
}
Explanation:
- Combines controller + Riverpod state
- Uses notifier for actions
- Handles UI state cleanly
When to Use
Use ConsumerStatefulWidget when:
- You need controllers (TextEditingController, AnimationController)
- You rely on lifecycle methods (initState, dispose)
- You need to trigger side effects
- You combine local + global state
- You handle forms, animations, or subscriptions
When NOT to Use
Avoid ConsumerStatefulWidget when:
- No local state is needed → use
ConsumerWidget - You only display provider data
- Widget is purely declarative
- No lifecycle logic is required
Best Practices
- Use
ref.read()in lifecycle methods, notwatch() - Dispose controllers properly in
dispose() - Keep
build()focused on UI only - Move business logic into providers
- Avoid heavy logic inside state class
- Prefer
ConsumerWidgetunless StatefulWidget is necessary
Common Mistakes
Using ref.watch in initState
Wrong
@override
void initState() {
ref.watch(counterProvider);
super.initState();
}
Why it's wrong:
watch cannot be used outside build lifecycle.
Correct
ref.read(counterProvider);
Putting Logic Inside Build
Wrong
build(...) {
ref.read(provider).fetchData();
}
Causes repeated side effects on rebuild.
Correct
Use initState or provider logic instead.
Not Disposing Controllers
Wrong
final controller = TextEditingController();
No cleanup leads to memory leaks.
Correct
@override
void dispose() {
controller.dispose();
super.dispose();
}
Related APIs
ConsumerWidgetConsumerWidgetRefref.watch()ref.read()NotifierProviderAsyncValue
Summary
ConsumerStatefulWidget is a StatefulWidget integrated with Riverpod. It allows you to combine Flutter lifecycle methods with reactive provider access, making it ideal for forms, controllers, and side-effect-heavy UI components.