provider_kit is a state management toolkit for Flutter, built to work seamlessly with the provider package. It simplifies state handling with predefined widgets, reduces boilerplate, and efficiently manages loading, error, and data states. With built-in async support, state observers, caching, and enhanced notifiers.
| π― Feature | π Description |
|---|---|
| πReduces Boilerplate Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Simplifies state management by minimizing repetitive code. |
| π Handles Multiple States | Provides a centralized way to manage loading, error, initial, empty, and data states while supplying predefined widgets for these states to be used within builders. |
| ποΈ Builders & Listeners | Enhanced widgets that integrate automatically with state changes and allow customization. |
| π Combined Provider States | Supports managing multiple provider states together. |
| πΎ State Caching | Provides mixins to store and restore state efficiently. |
| π οΈ Provider Observation | Monitors provider lifecycle events for better debugging. |
| π§© Immutable Objects | Ensures predictable state management through immutability. |
| β‘ Error & Loading Handling | Automatically manages loading and error states with built-in support. |
| π¦ Enhances Provider | Extends the functionality of the provider package. |
| π TypeDefs Convention | Type definitions use the providerβs name as a prefix for widgets and states, simplifying usage and improving readability. |
| Before | After |
|---|---|
![]() |
![]() |
- Getting started
- State
- View State
- Nested State Listener
- Notifier Observer
- Templates
- Best Practices for Managing Additional State in provider kit
dependencies:
provider_kit: ^0.2.0
provider: ^6.1.5Make sure to register your provider to gain full advantage of this package.
ChangeNotifierProvider(
create: (_) => MyProvider(),
child: ...
)For more information and details about registering your provider, see the documentation of provider package.
Alright, now lets dive in !
State management is simplified using StateNotifier (or any object implementing StateValueListenable) and various widgets designed for listening, building, and consuming state changes efficiently.
StateNotifier acts as the core class of this library, similar to ValueNotifier but with enhanced capabilities. By extending StateNotifier, our providers become observable, allowing widgets to listen and react to state changes.
Note: All state widgets in this package are generic over any object implementing StateValueListenable. StateNotifier is the default implementation provided by ProviderKit.
class MyProvider extends StateNotifier<int> {
CounterProvider() : super(0);
void increment() => state++;
void decrement() => state--;
}To listen to state changes from our provider, we use built-in widgets that are designed to interact with the StateNotifier. Each widget includes an optional provider attribute. By default, state widgets automatically search the widget tree for the corresponding provider type (e.g., MyProvider). Alternatively, we can pass a specific provider instance using the provider attribute.
Note: These widgets are not limited to
StateNotifier; any object implementingStateValueListenablecan be used.
- State Widgets include
StateListener,StateBuilder,StateConsumer.
A widget that listens for state changes and executes side effects without rebuilding the UI.
StateListener<MyProvider, MyDataType>(
provider: provider, // Optional
listenWhen: (previous, current) => previous != current, // Default, optional
shouldCallListenerOnInit: false, // Default, optional
listener: (context, state) {
// Can execute side effects here
},
child: YourWidget(),
);A widget in which the builder will be triggered on state change.
StateBuilder<MyProvider, MyDataType>(
provider: provider, // Optional
rebuildWhen: (previous, current) => previous != current, // Default, optional
builder: (context, state, child) {
return Text('Count: $state');
},
child: YourStaticWidget(), // Optional, won't be rebuilt
);A widget that combines the features of both StateListener and StateBuilder.
StateConsumer<MyProvider, MyDataType>(
provider: provider,
listenWhen: (previous, current) => previous != current, // Default, optional
shouldCallListenerOnInit: false, // Default, optional
listener: (context, state) {
// Can execute side effects here
},
rebuildWhen: (previous, current) => previous != current, // Default, optional
builder: (context, state, child) {
return Text('Count: $state');
},
child: YourStaticWidget(), // Optional, won't be rebuilt
);π‘ Tip: Passing a StateValueListenable instance directly (such as a StateNotifier)? Use NotifierBuilder, NotifierListener, or NotifierConsumer instead of StateBuilder, StateListener, or StateConsumer to avoid repeatedly typing <StateValueListenable<T>, T>.
// β Clean NotifierBuilder<int>(provider: counterProvider, builder: ...) // β Verbose StateBuilder<StateValueListenable<int>, int>(provider: counterProvider, builder: ...)For automatic provider lookup, use the original widgets instead.
With Multi State Widgets, we can listen to the states of multiple providers using a single widget. However, these widgets won't try to read the provider.
Note: The providers' states can be of the same type or different types (
dynamic).
The providers themselves are not limited toStateNotifier; any object implementingStateValueListenablecan be used.
- Multi State Widgets include
MultiStateListener,MultiStateBuilderandMultiStateConsumer.
A widget that listens to the state of multiple providers, and a state change in any of the providers will trigger the listener callback.
MultiStateListener<MyDataType>(
providers: [provider1, provider2, provider3],
listenWhen: (previous, current) => previous != current, // Default, optional
shouldCallListenerOnInit: false, // Default, optional
listener: (context, states) {
// Can execute side effects here
},
child: YourWidget(),
);A widget that listens to the state of multiple providers, and a state change in any of the providers will trigger the builder.
MultiStateBuilder<MyDataType>(
providers: [provider1, provider2, provider3],
rebuildWhen: (previous, current) => previous != current, // Default, optional
builder: (context, states, child) => Text(states.toString()),
child: YourStaticWidget(), // Optional, won't be rebuilt
);A widget that combines both the features of MultiStateListener and MultiStateBuilder.
MultiStateConsumer<MyDataType>(
providers: [provider1, provider2, provider3],
listenWhen: (previous, current) => previous != current, // Default, optional
shouldCallListenerOnInit: false, // Default, optional
listener: (context, states) {
// Can execute side effects here
},
rebuildWhen: (previous, current) => previous != current, // Default, optional
builder: (context, states, child) {
return Text('Count: $states');
},
child: YourStaticWidget(), // Optional, won't be rebuilt
);ViewState is a sealed class representing different states of a view. It supports various states such as Initial, Loading, Data, Empty, and Error. Each state has specific properties and behaviors.
A Typical use case for ViewState is when fetching data asynchronously. For example, it can be from a server or local storage. It can also be used in operation-based scenarios like authentication features.
| State | Description | Properties |
|---|---|---|
InitialState |
Represents the initial state of a view. | None |
LoadingState |
Represents a loading state with optional progress and message. | message: String?, progress: double? |
DataState |
Represents a successful data state containing the result object. | dataObject: T |
EmptyState |
Represents an empty state with an optional message. | message: String? |
ErrorState |
Represents an error state with an optional message and retry callback. | message: String?, onRetry: VoidCallback?, exception: dynamic, stackTrace: StackTrace? |
Important Note:
EmptyStatewill be used only forIterabledata types. For Example when your T is aList,Setetc.
ViewStateNotifier is a StateNotifier that manages ViewState<T>. It simplifies state management by handling various states such as loading, empty, data, and error for a given data type.
By default the initial state of
ViewStateNotifieris LoadingState.
class MyViewStateProvider extends ViewStateNotifier<List<Item>> {
final Repository _repo = Repository();
MyViewStateProvider() : super(const InitialState()) {
init();
}
Future<void> init() async {
try {
state = const LoadingState();
final List<Item> items = await _repo.getItems(10);
if (!mounted) return; // Guard against disposal
if (items.isEmpty) {
state = const EmptyState();
return;
}
state = DataState(items);
} catch (e, s) {
state = ErrorState(e.toString(), e, s, onRefresh);
}
}
void onRefresh() {
state = const LoadingState();
init();
}
}Note: Use
mountedto check whether the notifier is still alive before updating state after asynchronous operations. This prevents "used after disposed" errors.
Tired of manually implementing the same logic for every provider? No worries! Introducing AsyncViewStateNotifierβa more efficient way to manage our view state.
AsyncViewStateNotifier automates state management, eliminating the need to repeatedly extend ViewStateNotifier and implement the same boilerplate logic. It streamlines fetching, handling empty states, error management, and retry mechanisms.
By default the initial state of
AsyncViewStateNotifieris LoadingState.
Instead of writing the entire MyViewStateProvider which we seen above, we can simply extend AsyncViewStateNotifier like this:
class MyViewStateProvider extends AsyncViewStateNotifier<List<Item>> {
@override
FutureOr<List<Item>> fetchData() => Repository().getItems(10);
}
That's it! π
β
Automatically fetches data upon initialization.
β
Transitions to LoadingState before fetching.
β
If the data is Iterable and if its empty, it switches to EmptyState.
β
Catches exceptions and converts them into ErrorState.
β
Includes a built-in onRefresh function, which rebuilds the initialization logic.
β
Passes the onRefresh function, exception, and stack trace to ErrorState.
β
Internally guarded with mounted β For safe async state updates.
Note
FlutterErrorexceptions are reβthrown and not converted toErrorState. This ensures that fatal programming errors (e.g., assertion failures) are not masked by the UI.
With AsyncViewStateNotifier, state management becomes cleaner, more efficient, and hassle-free.
| Attributes | Type | Description |
|---|---|---|
| Constructor Params | ||
initialState |
ViewState<T> |
The initial state of the provider. Defaults to LoadingState. |
disableEmptyState |
bool |
By default, if T is an Iterable (like List, Set, etc.), an empty iterable will result in EmptyState. Setting this to true forces an empty iterable to be assigned as DataState. |
| Property | ||
state |
ViewState<T> |
The current state of the provider, which can be LoadingState, DataState, EmptyState, or ErrorState. |
| Methods | ||
init() |
FutureOr<void> |
Runs on initialization, setting up states and Guarded with Try catch. It won't execute again if already initialized unless refresh is called. |
fetchData() |
FutureOr<T> |
Fetches data from an API or database. Must be implemented in subclasses. |
errorStateObject() |
ErrorState<T> |
Helps to customize default ErrorState Object |
loadingStateObject() |
LoadingState<T> |
Helps to customize default LoadingState Object |
emptyStateObject() |
EmptyState<T> |
Helps to customize default EmptyState Object instance. |
refresh() |
Future<void> |
Refreshes the provider which will call init with fetchData() again. |
Lets customize our MyViewStateProvider to the fullest.
class MyViewStateProvider extends AsyncViewStateNotifier<List<Item>> {
// by default `initialState` is `LoadingState`.
// by default `disableEmptyState` is false.
MyViewStateProvider()
: super(initialState: const InitialState(),
//disabling empty state will set the state to `DataState` instead of `EmptyState`
disableEmptyState: true);
@override
FutureOr<void> init() async {
// `init` is internally guarded
// Custom initialization logic goes here
state = const LoadingState();
List<Item> items = await fetchData();
if (!mounted) return; // Guard against disposal
// Additional processing, such as filtering, can be done here
state = DataState(items);
}
@override
FutureOr<List<Item>> fetchData() async {
// Fetch data from an API or database
return [];
}
/// **Custom error state handling**
@override
ErrorState<List<Item>> errorStateObject(Object error, StackTrace stackTrace) {
String message = "Something went wrong";
// Custom error message handling
if (error is MyException) {
message = error.message;
}
return ErrorState<List<Item>>(message, error, stackTrace, refresh);
}
/// **Custom loading state**
@override
LoadingState<List<Item>> loadingStateObject() {
return const LoadingState<List<Item>>('Data is Loading...');
}
/// **Custom empty state**
@override
EmptyState<List<Item>> emptyStateObject() {
return const EmptyState<List<Item>>('No data available.');
}
/// **Optional refresh override**
@override
Future<void> refresh() async {
// Perform any additional refresh logic if needed
super.refresh();
}
}Note: Even if
refreshis not passed inside theErrorStateforretrymechanism, therefreshwill be automatically be read by theView State Widgetsas long as the provider extendsAsyncViewStateNotifier.
In a typical application, most screens fetch data from a server or local storage. On every view screen, we compare the state and display the appropriate widget based on that state. For example:
LoadingWidgetwhen the state is loadingErrorWidgetwhen the state is errorEmptyWidgetwhen the data list is emptyDataWidgetwhen the data is successfully fetched
Instead of checking the state type and passing the respective widgets for every single screen, we can reuse the same widgets across all screens. We can streamline this process by wrapping our MaterialApp with ViewStateWidgetsProvider and supplying custom widgets for each state.
Note: These widgets will be used internally by
ViewStateBuilder,ViewStateConsumer,MultiViewStateBuilderandMultiViewStateConsumerwhich weβll explore soon below.
ViewStateWidgetsProvider is simply an inherited widget that provides consistent state based widgets across our app.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ViewStateWidgetsProvider(
//supply your initial state widget
initialStateBuilder: (isSliver) {
const widget = Center(child: Text("Initial State"));
return isSliver ? const SliverToBoxAdapter(child: widget) : widget;
},
//supply your empty state widget
emptyStateBuilder: (message, isSliver) {
Widget widget = Center(child: Text(message ?? "No Data Available"));
return isSliver ? SliverToBoxAdapter(child: widget) : widget;
},
//supply your error state widget
//onRetry will refresh the provider
errorStateBuilder: (errorMessage, onRetry, exception, stackTrace, isSliver) {
final widget = Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(errorMessage ?? "An error occurred",
style: const TextStyle(color: Colors.red)),
TextButton(
onPressed: onRetry, child: const Text("Retry")),
],
),
);
return isSliver ? const SliverToBoxAdapter(child: widget) : widget;
},
//supply your loading state widget
loadingStateBuilder: (message, progress, isSliver) {
const widget = Center(child: CircularProgressIndicator());
return isSliver ? const SliverToBoxAdapter(child: widget) : widget;
},
child: const MaterialApp(
//..
),
);
}
}
Additionally, you can wrap any section of your widget tree with ViewStateWidgetsProvider to completely redefine its state widgets, or use ViewStateWidgetsProvider.override to update only specific state builders while inheriting the rest from the parent ViewStateWidgetsProvider.
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return ViewStateWidgetsProvider.override(
context: context,
// Overrides ONLY the loading builder for this subtree, used internally by `ViewStateWidgets`.
loadingStateBuilder: (message, progress, isSliver) {
const widget = Center(child: ProfileSkeletonLoader());
return isSliver ? const SliverToBoxAdapter(child: widget) : widget;
},
child: const ProfileView(),
);
}
}Note: In
errorStateBuilder, theerrorMessage,onRetry,exception, andstackTraceare automatically passed to the function if your provider isproviderKit.
These widgets are similar to State Widgets but are designed to adapt based on the corresponding ViewState. They listen to a provider that extends either ViewStateNotifier or AsyncViewStateNotifier, ensuring they respond dynamically to state changes.For example MyViewStateProvider which we learned above.
- View State Widgets includes
ViewStateListener,ViewStateBuilder,ViewStateConsumer.
This widget provides individual listener callbacks for each ViewState, allowing customized behavior based on the current state.
ViewStateListener<MyViewStateProvider, MyDataType(
dataStateListener: (data) => context.showToast(data.toString()),
child: YourWidget(),
)| Attribute Name | Type | Required/Optional | Description |
|---|---|---|---|
provider |
P? |
Optional | Automatically searches the widget tree for the corresponding provider type (e.g., MyViewStateProvider) if not provided. |
initialStateListener |
void Function()? |
Optional | Invoked when the state is InitialState. |
loadingStateListener |
void Function(String? message, double? progress)? |
Optional | Invoked when the state is LoadingState. |
dataStateListener |
void Function(T data)? |
Required | Invoked when the state is DataState. |
emptyStateListener |
void Function(String? message)? |
Optional | Invoked when the state is EmptyState. |
errorStateListener |
void Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace)? |
Optional | Invoked when the state is ErrorState. |
listenWhen |
bool Function(ViewState<T> previous, ViewState<T> next)? |
Optional | Determines whether to listen for state changes based on previous and next state comparisons. |
shouldCallListenerOnInit |
bool |
Optional | Determines whether the state listener should be called immediately upon initialization. Defaults to false. |
child |
Widget? |
Required | The child widget wrapped by ViewStateListener. |
Each callback is triggered based on the current ViewState, allowing dynamic response handling within ViewStateListener.
This widget provides individual builder for each ViewState, allowing customized behavior based on the current state.
Important Note:
initialStateBuilder,loadingStateBuilder,emptyStateBuilderanderrorStateBuilderthat we supplied toViewStateWidgetsProviderwill be used by this widget internally by default.
ViewStateBuilder<MyViewStateProvider, MyDataType>(
// Other ViewState builders will be assigned from the `ViewStateWidgetsProvider`.
// We can override them here in `ViewStateBuilder` if needed.
// loadingBuilder: (message, progress, isSliver) => ,
dataBuilder: (data) => Text(data.toString()),
)The ViewStateBuilder allows customization of UI rendering for different ViewStates, enabling dynamic UI updates based on the current state.
| Attribute Name | Type | Required/Optional | Description |
|---|---|---|---|
provider |
P? |
Optional | Automatically searches the widget tree for the corresponding provider type if not provided. |
rebuildWhen |
bool Function(ViewState<T> previous, ViewState<T> next)? |
Optional | Determines if the builder should rebuild based on state changes. |
initialBuilder |
Widget Function(bool isSliver)? |
Optional | Called when the state is InitialState. |
dataBuilder |
Widget Function(T data) |
Required | Called when the state is DataState, passing the retrieved data. |
errorBuilder |
Widget Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace, bool isSliver)? |
Optional | Called when the state is ErrorState. |
loadingBuilder |
Widget Function(String? message, double? progress, bool isSliver)? |
Optional | Called when the state is LoadingState. |
emptyBuilder |
Widget Function(String? message, bool isSliver)? |
Optional | Called when the state is EmptyState. |
isSliver |
bool |
Optional | Specifies whether the widget is a sliver. Defaults to false. |
child |
Widget? |
Optional | A static child widget that does not depend on the state. |
This widget combines features of both ViewStateListener and ViewStateBuilder. We can use this widget when we need both listeners and builders functionality.
Important Note:
initialStateBuilder,loadingStateBuilder,emptyStateBuilderanderrorStateBuilderthat we supplied toViewStateWidgetsProviderwill be used by this widget internally by default.
ViewStateConsumer<MyViewStateProvider, MyDataType(
dataStateListener: (data) {
print(data);
},
dataBuilder: (data) => Text(data.toString()),
)| Attribute Name | Type | Required/Optional | Description |
|---|---|---|---|
provider |
P? |
Optional | Automatically searches the widget tree for the corresponding provider type. |
initialStateListener |
void Function()? |
Optional | Invoked when the state is InitialState. |
loadingStateListener |
void Function(String? message, double? progress)? |
Optional | Invoked when the state is LoadingState. |
dataStateListener |
void Function(T data)? |
Optional | Invoked when the state is DataState. |
emptyStateListener |
void Function(String? message)? |
Optional | Invoked when the state is EmptyState. |
errorStateListener |
void Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace)? |
Optional | Invoked when the state is ErrorState. |
listenWhen |
bool Function(ViewState<T> previous, ViewState<T> next)? |
Optional | Determines whether to listen for state changes based on previous and next state comparisons. |
rebuildWhen |
bool Function(ViewState<T> previous, ViewState<T> next)? |
Optional | Determines if the builder should rebuild based on state changes. |
initialBuilder |
Widget Function(bool isSliver)? |
Optional | Called when the state is InitialState. |
loadingBuilder |
Widget Function(String? message, double? progress, bool isSliver)? |
Optional | Called when the state is LoadingState. |
emptyBuilder |
Widget Function(String? message, bool isSliver)? |
Optional | Called when the state is EmptyState. |
dataBuilder |
Widget Function(T data) |
Required | Called when the state is DataState, passing the retrieved data. |
errorBuilder |
Widget Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace, bool isSliver)? |
Optional | Called when the state is ErrorState. |
isSliver |
bool |
Optional | Specifies whether the widget is a sliver. Defaults to false. |
Multi View State Widgets allow us to listen to multiple providers' ViewStates with a single widget. However, these widgets do not read the provider.
Note: Our providers states can either be of the same types or dynamic.
Key Difference: Unlike
ViewStateListener,ViewStateBuilder, andViewStateConsumer, Multi View State Widgets require a list of providers as a mandatory attribute.
- Multi View State Widgets inlcudes
MultiViewStateListener,MultiViewStateBuilderandMultiViewStateConsumer.
The behavior of MultiViewStateBuilder, MultiViewStateListener, and MultiViewStateConsumer depends on the collective states of the provided ViewStates. The highest-priority state in the list determines which builder or listener is triggered.
- If any provider is in
ErrorState, theerrorStateListener(orerrorBuilder) will be invoked. -
The first encountered
ErrorStatedata will be passed to theerrorStatelistenerorerrorBuilder.
- If no
ErrorStateis found, but at least one provider is inInitialState, theinitialStateListener(orinitialBuilder) will be invoked.
- If no
ErrorStateorInitialStateexists, but at least one provider is inLoadingState, theloadingStateListener(orloadingBuilder) will be invoked. -
First encountered
LoadingStatemessage will be passed to theloadingStatelistenerorloadingBuilder. -
progresswill be aggregated from allLoadingStates into a single combined value.
- If none of the above states are present, but at least one provider is in
EmptyState, theemptyStateListener(oremptyBuilder) will be invoked. -
The first encountered
EmptyStatemessage will be passed to theemptyStatelisteneroremptybuilder.
- Only If all providers are in
DataState, thedataStateListener(ordataBuilder) will be invoked.
- First encountered state applies to all states except
DataState. LoadingStateprogress is aggregated from all activeLoadingStates into a single combined value.- Modifying
listenWhenorrebuildWhenoverrides the default priority logic which will results in triggeringlistenerorbuilderwhenever any provider's state changes.
If some providers have data while others return empty, triggering
EmptyStatemay not be ideal.
Solution: Avoid using EmptyState in the provider logic. Instead, handle empty cases manually inside dataBuilder.
This ensures EmptyState wonβt be triggered unless all providers return an empty state.
The MultiViewStateListener allows listening to multiple ViewState providers simultaneously. It merges their states into a unified ViewState, enabling centralized state management without manually handling multiple providers.
Check How Multi View State Widgets Work for more detailed information about how which state is triggered
MultiViewStateListener<MyDataType>(
providers: [viewStateProviderOne, viewStateProviderTwo, viewStateProviderThree],
dataStateListener: (dataStates) {
print(dataStates);
},
child: YourChild(),
);| Attribute Name | Type | Required/Optional | Description |
|---|---|---|---|
providers |
List<ViewStateNotifier<T>> |
Required | A list of ViewStateNotifier providers that the listener will observe. |
initialStateListener |
void Function()? |
Optional | Callback triggered when the state transitions to InitialState. |
loadingStateListener |
void Function(String? message, double? progress)? |
Optional | Invoked when the state is LoadingState. Receives a message and an aggregated progress value (if multiple providers are loading). |
emptyStateListener |
void Function(String? message)? |
Optional | Triggered when the state is EmptyState. Uses the first encountered EmptyState's empty message. |
errorStateListener |
void Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace)? |
Optional | Called when the state transitions to ErrorState, passing the first encountered error details. |
dataStateListener |
void Function(List<DataState<T>> dataStates)? |
Optional | Called when all providers transition to DataState, providing the combined list of data states. |
listenWhen |
bool Function(List<ViewState<T>> previous, List<ViewState<T>> next) |
Optional | Modifying this overrides the default priority logic, triggering listener whenever any provider's state changes |
shouldCallListenerOnInit |
bool |
Optional | Determines whether the listener should be triggered immediately when the widget initializes. Defaults to false. |
child |
Widget? |
Required | The wrapped widget that remains within the listener, receiving state updates. |
The MultiViewStateBuilder enables building UI based on multiple ViewState providers simultaneously. It merges their states into a unified ViewState.
Important Note:
initialStateBuilder,loadingStateBuilder,emptyStateBuilderanderrorStateBuilderthat we supplied toViewStateWidgetsProviderwill be used by this widget internally by default.
MultiViewStateBuilder<MyDataType>(
providers: [viewStateProviderOne, viewStateProviderTwo, viewStateProviderThree],
dataBuilder: (dataStates) {
return YourWidget(dataStates);
},
);| Attribute Name | Type | Required/Optional | Description |
|---|---|---|---|
providers |
List<ViewStateNotifier<T>> |
Required | A list of ViewStateNotifier providers that the builder will observe. |
initialBuilder |
Widget Function(bool isSliver)? |
Optional | Builder triggered when the state transitions to InitialState. |
loadingBuilder |
Widget Function(String? message, double? progress, bool isSliver)? |
Optional | Triggered when the state is LoadingState. Receives a message and an aggregated progress value (if multiple providers are loading). |
emptyBuilder |
Widget Function(String? message, bool isSliver)? |
Optional | Triggered when the state is EmptyState. Uses the first encountered EmptyState's empty message. |
errorBuilder |
Widget Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace, bool isSliver)? |
Optional | Triggered when the state transitions to ErrorState, passing the first encountered error details. |
dataBuilder |
Widget Function(List<DataState<T>> dataStates)? |
Required | Triggered when all providers transition to DataState, providing the combined list of data states. |
rebuildWhen |
bool Function(List<ViewState<T>> previous, List<ViewState<T>> next)? |
Optional | Modifying this overrides the default priority logic, triggering builder whenever any provider's state changes. |
isSliver |
bool? |
Optional | Determines whether the widget should be a Sliver or a regular widget. Defaults to false. |
Combines the features of MultiViewStateListener and MultiViewStateBuilder in a single widget.
Important Note:
initialStateBuilder,loadingStateBuilder,emptyStateBuilderanderrorStateBuilderthat we supplied toViewStateWidgetsProviderwill be used by this widget internally by default.
MultiViewStateConsumer<MyDataType>(
providers: [viewStateProviderOne, viewStateProviderTwo, viewStateProviderThree],
dataStateListener: (dataStates) {
print(dataStates);
},
dataBuilder: (dataStates) {
return YourWidget(dataStates);
},
);| Attribute Name | Type | Required/Optional | Description |
|---|---|---|---|
providers |
List<ViewStateNotifier<T>> |
Required | A list of ViewStateNotifier providers that the consumer will observe. |
initialStateListener |
void Function()? |
Optional | Callback triggered when the state transitions to InitialState. |
loadingStateListener |
void Function(String? message, double? progress)? |
Optional | Invoked when the state is LoadingState. Receives a message and an aggregated progress value (if multiple providers are loading). |
emptyStateListener |
void Function(String? message)? |
Optional | Triggered when the state is EmptyState. Uses the first encountered EmptyState's empty message. |
errorStateListener |
void Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace)? |
Optional | Called when the state transitions to ErrorState, passing the first encountered error details. |
dataStateListener |
void Function(List<DataState<T>> dataStates)? |
Optional | Called when all providers transition to DataState, providing the combined list of data states. |
listenWhen |
bool Function(List<ViewState<T>> previous, List<ViewState<T>> next) |
Optional | Modifying this overrides the default priority logic, triggering listener whenever any provider's state changes. |
shouldCallListenerOnInit |
bool |
Optional | Determines whether the listener should be triggered immediately when the widget initializes. Defaults to false. |
initialBuilder |
Widget Function(bool isSliver)? |
Optional | Builder triggered when the state transitions to InitialState. |
loadingBuilder |
Widget Function(String? message, double? progress, bool isSliver)? |
Optional | Triggered when the state is LoadingState. Receives a message and an aggregated progress value (if multiple providers are loading). |
emptyBuilder |
Widget Function(String? message, bool isSliver)? |
Optional | Triggered when the state is EmptyState. Uses the first encountered EmptyState's empty message. |
errorBuilder |
Widget Function(String? message, VoidCallback? onRetry, dynamic exception, StackTrace? stackTrace, bool isSliver)? |
Optional | Triggered when the state transitions to ErrorState, passing the first encountered error details. |
dataBuilder |
Widget Function(List<DataState<T>> dataStates)? |
Required | Triggered when all providers transition to DataState, providing the combined list of data states. |
rebuildWhen |
bool Function(List<ViewState<T>> previous, List<ViewState<T>> next)? |
Optional | Modifying this overrides the default priority logic, triggering builder whenever any provider's state changes. |
isSliver |
bool? |
Optional | Determines whether the widget should be a Sliver or a regular widget. Defaults to false. |
Some mixins to help with ViewState caching and data caching that will come handy.
This mixin can be used on provider with ViewState support like ViewStateNotifier or AsyncViewStateNotifier. It provides caching capabilities for different view states. It keeps track of the most recent state of each type and allows easy retrieval of cached states.
- Stores the last known state for each
ViewStatetype. - Allows accessing cached states via getter methods.
- Clears cached states when disposed to free up memory.
class MyViewStateProvider extends ViewStateNotifier<MyDataType> with ExViewStateCacheMixin {
// Your implementation here
}| Name | Type | Description |
|---|---|---|
exInitialState |
InitialState<T>? |
Stores the last InitialState. |
exLoadingState |
LoadingState<T>? |
Stores the last LoadingState. |
exEmptyState |
EmptyState<T>? |
Stores the last EmptyState. |
exErrorState |
ErrorState<T>? |
Stores the last ErrorState. |
exDataState |
DataState<T>? |
Stores the last DataState. |
exDataStateObject |
T? |
Stores the last known data object from DataState. |
clearCache() |
void |
Clears all cached states. |
This mixin can be used on provider with ViewState support like ViewStateNotifier or AsyncViewStateNotifier. We can use this mixin to cache original data.
sometimes we do local filtering on data we fetched from server and when user cancel filter we need to show the original data back which is exactly when we should use this mixin.
- Stores the latest
DataState<T>and data whensaveDataStateCopyis called. - Provides access to the cached
DataState<T>and its data object. - Allows clearing cached state manually using
clearDataStateCopy.
class MyViewStateProvider extends AsyncViewStateNotifier<List<String>> with DataStateCopyCacheMixin {
void updateDataState(List<String> newData) {
final newState = DataState(newData);
saveDataStateCopy(newState);
state = newState;
}
void clearFilter(){
state = dataStateCopy!;
}
}| Name | Type | Description |
|---|---|---|
dataStateCopy |
DataState<T>? |
gets the copy of the saved DataState<T>. |
dataObjectCopy |
T? |
gets the copy of the saved data object from DataState<T>. |
saveDataStateCopy |
(ViewState<T>? newDataState) |
Stores the given DataState<T> and its associated data. |
clearDataStateCopy |
void |
Clears the stored DataState<T> and its associated data. |
NestedStateListener is a widget that nests multiple state listeners within a single widget. It allows you to combine different types of listeners and manage them together efficiently.
- Supports nesting multiple state listeners.
- Works seamlessly with
StateListener,ViewStateListener,MultiStateListener, andMultiViewStateListener. - Reduces boilerplate code by combining multiple listeners into a single widget.
NestedStateListener(
listeners: [
StateListener<MyProvider,DataType>(
listener: (context, state) {
// Handle state changes
},
),
MultiStateListener<DataType>(
providers: [ProviderOne(),ProviderTwo()],
listener: (context, states) {
// Handle state changes
},
),
ViewStateListener<MyProvider,DataType(
dataStateListener: (data) {
// Handle view state changes
},
),
MultiViewStateListener<DataType>(
providers: [ProviderOne(),ProviderTwo()],
dataStateListener: (states) {
// Handle state changes
},
),
],
child: MyChildWidget(),
);| Attribute | Type | Description |
|---|---|---|
listeners (Required) |
List<SingleChildWidget> |
A list of listeners to be applied. These can include StateListener, ViewStateListener, MultiStateListener, and MultiViewStateListener. |
child (Required) |
Widget |
The child widget that will be wrapped by the listeners. |
Note: Ensure that the
listenerslist contains at least one listener to avoid an empty nesting.
The NotifierObserver helps you monitor the lifecycle of all notifiers in your application.
It can be used for debugging, logging, analytics, or any other crossβcutting concern β it receives callbacks whenever a notifier is created, changes state, throws an error, or is disposed.
Assign an implementation of NotifierObserver to the static observer field on NotifierBase.
This is typically done at the start of your app, before running the MaterialApp.
void main() {
// Set the global observer
NotifierBase.observer = MyNotifierObserver();
runApp(const MyApp());
}
class MyNotifierObserver extends NotifierObserver {
@override
void onChange(NotifierBase notifier, Change change) {
super.onChange(notifier, change);
debugPrint(
'notifier onChange -- \${notifier.runtimeType}, '
'\${change.currentState.runtimeType} ---> \${change.nextState.runtimeType}',
);
}
@override
void onCreate(NotifierBase notifier) {
super.onCreate(notifier);
debugPrint('notifier onCreate -- \${notifier.runtimeType}');
}
@override
void onError(
NotifierBase notifier, Object error, StackTrace stackTrace) {
debugPrint(
'notifier onError -- \${notifier.runtimeType} '
'Error: \$error StackTrace: \$stackTrace',
);
super.onError(notifier, error, stackTrace);
}
@override
void onDispose(NotifierBase notifier) {
super.onDispose(notifier);
debugPrint('notifier onDispose -- \${notifier.runtimeType}');
}
}This guide provides step-by-step instructions for setting up the ProviderKit Template in VS Code and Android Studio/IntelliJ on both Mac & Windows.
- Open VS Code.
- Press
Cmd + Shift + P(Mac) orCtrl + Shift + P(Windows). - Type
"Snippets: Configure Snippets"and select it. - Choose
dart.jsonto open the Dart snippets file.
-
Copy the following snippet:
{ "AsyncViewStateNotifier Template": { "prefix": "pkit", "description": "AsyncViewStateNotifier template", "body": [ "import 'dart:async';", "", "import 'package:provider_kit/provider_kit.dart';", "", "class ${1:ProviderName}Provider extends AsyncViewStateNotifier<${2:DataType}> {", "", " @override", " FutureOr<${2:DataType}> fetchData() async {", " return ;", " }", "", "}", "", "typedef ${1:ProviderName}ViewState = ViewState<${2:DataType}>;", "", "typedef ${1:ProviderName}InitialState = InitialState<${2:DataType}>;", "typedef ${1:ProviderName}LoadingState = LoadingState<${2:DataType}>;", "typedef ${1:ProviderName}EmptyState = EmptyState<${2:DataType}>;", "typedef ${1:ProviderName}DataState = DataState<${2:DataType}>;", "typedef ${1:ProviderName}ErrorState = ErrorState<${2:DataType}>;", "", "typedef ${1:ProviderName}ViewStateBuilder = ViewStateBuilder<${1:ProviderName}Provider, ${2:DataType}>;", "typedef ${1:ProviderName}ViewStateListener = ViewStateListener<${1:ProviderName}Provider, ${2:DataType}>;", "typedef ${1:ProviderName}ViewStateConsumer = ViewStateConsumer<${1:ProviderName}Provider, ${2:DataType}>;" ] } } -
Paste it inside
dart.json. -
Save the file (Cmd + S on Mac, Ctrl + S on Windows).
- Open any Dart file.
- Type
"pkit"and pressTabto insert the template.
-
Pressing
Tabshould move the cursor to the next snippet placeholder (e.g., fromProviderNametoDataType). -
If
Tabdoes not move to the next placeholder, follow these troubleshooting steps:-
Check your settings:
- Open VS Code Settings (
Ctrl + ,orCmd + ,). - Search for
"Tab Completion"and set it toonlySnippets.
- Open VS Code Settings (
-
Restart VS Code after changing the setting.
-
Disable conflicting extensions:
- If you have Tabnine VS Code extension, try disabling it.
- Some AI-powered extensions override
Tabbehavior.
-
- Open IntelliJ IDEA or Android Studio.
- Go to Settings (
Ctrl + Alt + Son Windows/Linux,Cmd + ,on Mac). - Navigate to Editor β Live Templates.
- Click on the + (Add) button.
- Select Template Group.
- Name it ProviderKit.
- Paste the below code after selecting the created group.
<template name="providerkit" value="import 'dart:async'; import 'package:provider_kit/provider_kit.dart'; class $NAME$Provider extends AsyncViewStateNotifier<$DATA_TYPE$> { @override FutureOr<$DATA_TYPE$> fetchData() async { return ; } } typedef $NAME$ViewState = ViewState<$DATA_TYPE$>; typedef $NAME$InitialState = InitialState<$DATA_TYPE$>; typedef $NAME$LoadingState = LoadingState<$DATA_TYPE$>; typedef $NAME$EmptyState = EmptyState<$DATA_TYPE$>; typedef $NAME$DataState = DataState<$DATA_TYPE$>; typedef $NAME$ErrorState = ErrorState<$DATA_TYPE$>; typedef $NAME$ViewStateBuilder = ViewStateBuilder<$NAME$Provider, $DATA_TYPE$>; typedef $NAME$ViewStateListener = ViewStateListener<$NAME$Provider, $DATA_TYPE$>; typedef $NAME$ViewStateConsumer = ViewStateConsumer<$NAME$Provider, $DATA_TYPE$>;" description="AsyncViewStateNotifier template" toReformat="false" toShortenFQNames="true">
<variable name="NAME" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="DATA_TYPE" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="DART" value="true" />
<option name="FLUTTER" value="true" />
</context>
</template>- Open a Dart file.
- Type
pkitand press Tab or Enter. - The template expands, allowing you to fill in
NAMEandDATA_TYPE.
AsyncViewStateNotifier provides a structured way to manage view states efficiently. Below is a template that allows you to create a FeedProvider using AsyncViewStateNotifier.
import 'dart:async';
import 'package:provider_kit/provider_kit.dart';
class FeedProvider extends AsyncViewStateNotifier<List<Item>> {
@override
FutureOr<List<Item>> fetchData() async {
return []; // Fetch data from an API or database
}
}
typedef FeedViewState = ViewState<List<Item>>;
typedef FeedInitialState = InitialState<List<Item>>;
typedef FeedLoadingState = LoadingState<List<Item>>;
typedef FeedEmptyState = EmptyState<List<Item>>;
typedef FeedDataState = DataState<List<Item>>;
typedef FeedErrorState = ErrorState<List<Item>>;
typedef FeedViewStateBuilder = ViewStateBuilder<FeedProvider, List<Item>>;
typedef FeedViewStateListener = ViewStateListener<FeedProvider, List<Item>>;
typedef FeedViewStateConsumer = ViewStateConsumer<FeedProvider, List<Item>>;Note: Above
FeedProvidergenerated by the template can be used for any View State Widgets and Multi View State Widgets.
Using typedefs in AsyncViewStateNotifier offers several advantages:
Instead of writing long generic types, typedefs make it easier to understand what each state represents:
FeedViewState viewState;compared to:
ViewState<List<Item>> viewState;By adding Feed in front of state widgets, it's easier to identify which provider they belong to. Example:
FeedViewStateBuilder(
builder: (context, state) {
// Handle state changes
},
)instead of:
ViewStateBuilder<FeedProvider, List<Item>>(
builder: (context, state) {
// Handle state changes
},
)Instead of specifying the provider and data type every time, typedefs allow you to use shorter, meaningful names.
By defining typedefs, you ensure that the correct data types are used throughout the app, preventing common mistakes.
Since provider_kit is built with ChangeNotifier as its base, there are multiple ways to use the provider.
When managing state in provider_kit, you may need additional parameters like pagination, filters, or metadata alongside your primary data (e.g., List<Item>). Here are three structured approaches to handle this efficiently:
Since provider_kit extends ChangeNotifier, you can declare additional variables inside the provider and update them using notifyListeners(). These variables can be listened to via Selector in the UI.
class MyProvider extends AsyncViewStateNotifier<List<Item>> {
PaginationData? paginationData;
FilterData? filterData;
void updatePagination(PaginationData newData) {
paginationData = newData;
notifyListeners();
}
}β Avoid this approach as it mixes multiple responsibilities within a single provider, making it harder to maintain and test.
A better approach is to store all related data inside DataState, ensuring clear separation of concerns.
DataState((
pagination: PaginationData(),
filter: FilterData(),
items: List<Item>(),
));β Advantages:
- Keeps all relevant data encapsulated in a single object.
- Improves maintainability and separation of concerns.
- Easier to test and manage.
A scalable approach is to keep pagination and filter logic in separate providers and link them using ProxyProvider.
class PaginationProvider extends AsyncViewStateNotifier<PaginationData> {}
class FilterProvider extends AsyncViewStateNotifier<FilterData> {}
ProxyProvider<PaginationProvider, MyProvider>(
update: (_, pagination, myProvider) =>
myProvider!..updatePagination(pagination.state),
)β Advantages:
- Encourages modularity and reusability.
- Keeps providers focused on a single responsibility.
- Enhances performance by updating only necessary state.
Few features of this package were inspired by
flutter_bloc.
Have a feature request or found a bug? Feel free to open an issue on the GitHub Issue Tracker. Your feedback helps improve ProviderKit!
ProviderKit is backed by a comprehensive automated test suite covering widgets, state management, listeners, edge cases, and other core package functionality.
Stay updated and reach out for collaborations!
Website: Ram Prasanth

