Toggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage
Core Concepts
Purpose: Riverpod is used for global state (shared across the app), while Hooks are used for local state and managing widget lifecycles (like controllers and timers) without needing a StatefulWidget.
The Key Widget: To use both in the same widget, you must extend HookConsumerWidget.
This gives you access to both the WidgetRef (to talk to Riverpod) and the ability to use hooks like useState or useEffect.
When to Use Each
Feature
Use Riverpod (Providers)
Use Flutter Hooks
State Scope
Global / Shared across screens
Local to a single widget
Examples
Auth status, User Profile, API data
Text controllers, Animation controllers, Scroll controllers
Benefit
Testable, decoupled logic
Removes “initState” and “dispose” boilerplate
Basic Usage Example
Using HookConsumerWidget allows you to combine global providers with local hooks:
import'package:flutter/material.dart';import'package:hooks_riverpod/hooks_riverpod.dart';import'package:flutter_hooks/flutter_hooks.dart';// A simple global provider
finalcounterProvider=StateProvider((ref)=>0);classMyWidgetextendsHookConsumerWidget{@overrideWidgetbuild(BuildContextcontext,WidgetRefref){// 1. Riverpod: Watch global state
finalcount=ref.watch(counterProvider);// 2. Hooks: Manage local state (no StatefulWidget needed)
finaltextController=useTextEditingController();returnColumn(children:[Text('Global Count: $count'),TextField(controller:textController),],);}}
Important Constraints
Call Order: Hooks must be called at the top level of the build method.
They cannot be called inside loops, conditions (if statements), or asynchronous blocks.
Inheritance: If you use a hook, your widget must extend HookWidget or HookConsumerWidget.
Using a hook in a standard StatelessWidget will cause a runtime crash.
Setup & Imports
To use code generation alongside hooks, ensure your widget extends HookConsumerWidget.
import'package:flutter/material.dart';import'package:flutter_hooks/flutter_hooks.dart';import'package:hooks_riverpod/hooks_riverpod.dart';// Required for Riverpod code generation boilerplate
part'my_widget.g.dart';
Modern Syntax (@riverpod & keepAlive)
The @riverpod annotation automatically handles provider types based on your function/class structure.
By default, auto-generated providers are auto-dispose (they destroy their state when no widgets are listening).
@riverpodclassCounterextends_$Counter{@overrideintbuild()=>0;// Initial state
voidincrement()=>state++;// Update state
}// Generates: counterProvider
Monitoring State in Hooks Widgets
Inside a HookConsumerWidget, use the WidgetRef ref parameter inside the build method alongside your local Flutter hooks.
classProfileScreenextendsHookConsumerWidget{constProfileScreen({super.key});@overrideWidgetbuild(BuildContextcontext,WidgetRefref){// -------------------------------------------------------------
// RIVERPOD: Monitoring Global State
// -------------------------------------------------------------
// 1. ref.watch : Rebuilds widget when the state changes (Best for UI)
finalcounter=ref.watch(counterProvider);// 2. ref.listen : Runs side-effects (snackbars, dialogs) without rebuilding UI
ref.listen<int>(counterProvider,(previous,next){if(next==10)ScaffoldMessenger.of(context).showSnackBar(constSnackBar(content:Text('Max reached!')));});// 3. ref.read : Safe to use inside buttons/callbacks to trigger actions
// Do NOT use ref.read directly in the build body
// -------------------------------------------------------------
// FLUTTER HOOKS: Monitoring Local Lifecycle/State
// -------------------------------------------------------------
// 1. useTextEditingController : Disposes itself automatically
finaltextController=useTextEditingController();// 2. useEffect : Simulates initState, didUpdateWidget, and dispose
useEffect((){print('Widget was mounted');return()=>print('Widget was disposed');// Cleanup function
},const[]);// Empty array means this runs only ONCE on mount
returnScaffold(body:Column(children:[Text('Global Count: $counter'),TextField(controller:textController),ElevatedButton(onPressed:()=>ref.read(counterProvider.notifier).increment(),child:constText('Increment Global'),),],),);}}