ReactiveNotifier
A flexible, elegant, and secure tool for state management in Flutter. Designed to easily integrate with architectural patterns like MVVM, it guarantees full independence from BuildContext and is suitable for projects of any scale.
Note: Are you migrating from
reactive_notify
? The API remains unchanged - just update your dependency toreactive_notifier
.
Features
- π Simple and intuitive API
- ποΈ Perfect for MVVM architecture
- π Independent from BuildContext
- π― Type-safe state management
- π‘ Built-in Async and Stream support
- π Smart related states system
- π οΈ Repository/Service layer integration
- β‘ High performance with minimal rebuilds
- π Powerful debugging tools
- π Detailed error reporting
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
reactive_notifier: ^2.3.1
Quick Start
Basic Usage with ReactiveNotifier and ReactiveBuilder.notifier
Example: Handling a Simple State
This example demonstrates how to manage a basic state that stores a String
. It shows how to declare the state, create a widget to display it, and update its value:
import 'package:flutter/material.dart';
import 'package:reactive_notifier/reactive_notifier.dart';
// Declare a simple state
final messageState = ReactiveNotifier<String>(() => "Hello, world!");
class SimpleExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ReactiveNotifier Example")),
body: Center(
child: ReactiveBuilder.notifier(
notifier: messageState,
builder: (context, value, keep) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
value, // Display the current state
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
keep(
ElevatedButton(
onPressed: () => messageState.updateState("New message!"),
child: Text("Update Message"),
),
),
],
);
},
),
),
);
}
}
Example Description
-
State Declaration:
ReactiveNotifier<String>
is used to manage a state of typeString
.- You can use other primitive types such as
int
,double
,bool
,enum
, etc.
-
ReactiveBuilder.notifier:
- Observes the state and automatically updates the UI when its value changes.
- Accepts three parameters in the
builder
method:context
: The widget's context.value
: The current value of the state.keep
: Prevents uncontrolled widget rebuilds.
-
State Update:
- The
updateState
method is used to change the state value. - Whenever the state is updated, the UI dependent on it automatically rebuilds.
- The
1. Model Class Definition
We'll create a MyClass
model with some properties such as String
and int
.
class MyClass {
final String name;
final int value;
MyClass({required this.name, required this.value});
// Method to create an empty instance
MyClass.empty() : name = '', value = 0;
// Method to clone and update values
MyClass copyWith({String? name, int? value}) {
return MyClass(
name: name ?? this.name,
value: value ?? this.value,
);
}
}
2. Create and Update State with ReactiveNotifier
Now, we'll use ReactiveNotifier
to manage the state of MyClass
. We'll start with an empty state and later update it using the updateState
method.
final myReactive = ReactiveNotifier<MyClass>(() => MyClass.empty());
Here, myReactive
is a ReactiveNotifier
managing the state of MyClass
.
3. Display and Update State with ReactiveBuilder.notifier
We'll use ReactiveBuilder.notifier
to display the current state of MyClass
and update it when needed.
import 'package:flutter/material.dart';
import 'package:reactive_notifier/reactive_notifier.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Direct ReactiveNotifier")),
body: Center(
child: ReactiveBuilder.notifier(
notifier: myReactive,
builder: (context, state, keep) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Name: ${state.name}"),
Text("Value: ${state.value}"),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Update the state with new values
myReactive.updateState(state.copyWith(name: "New Name", value: 42));
},
child: Text("Update State"),
),
],
);
},
),
),
),
);
}
}
Advantages of This Approach
- Simplicity: This example is straightforward and easy to understand, making it ideal for handling simple states without requiring a
ViewModel
or repository. - Reactivity: With
ReactiveNotifier
andReactiveBuilder.notifier
, the UI automatically updates whenever the state changes. - Immutability:
MyClass
follows the immutability pattern, simplifying state management and preventing unexpected side effects.
Shopping Cart with ViewModelStateImpl
1. Defining the Model
The CartModel
class represents the shopping cart's state. It contains the data and includes a copyWith
method for creating a new instance with updated values.
class CartModel {
final List<String> items;
final double total;
CartModel({this.items = const [], this.total = 0.0});
// Method to clone the model with updated values
CartModel copyWith({List<String>? items, double? total}) {
return CartModel(
items: items ?? this.items,
total: total ?? this.total,
);
}
}
2. ViewModel for Managing Cart Logic
The ViewModel
contains the logic for modifying the cart's state. Instead of using state.copyWith
, use value.copyWith
to access the current state and update it.
class CartViewModel extends ViewModelStateImpl<CartModel> {
CartViewModel() : super(CartModel());
// Function to add a product to the cart and update the total
void addProduct(String item, double price) {
final updatedItems = List<String>.from(value.items)..add(item);
final updatedTotal = value.total + price;
updateState(value.copyWith(items: updatedItems, total: updatedTotal));
}
// Function to empty the cart
void clearCart() {
updateState(CartModel());
}
}
3. Creating the ViewModel Instance
Create the ViewModel
instance that will manage the cart's state.
final cartViewModel = ReactiveNotifier<CartViewModel>(() => CartViewModel());
4. Widget to Display Cart State
Finally, create a widget that observes the cart's state and updates the UI whenever necessary.
import 'package:flutter/material.dart';
import 'package:reactive_notifier/reactive_notifier.dart';
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Shopping Cart")),
body: Center(
child: ReactiveBuilder<CartViewModel>(
valueListenable: cartViewModel.value,
builder: (context, viewModel, keep) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Products in Cart:"),
...viewModel.items.map((item) => Text(item)).toList(),
SizedBox(height: 20),
Text("Total: \$${viewModel.total.toStringAsFixed(2)}"),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
keep(
ElevatedButton(
onPressed: () {
// Add a new product
cartViewModel.value.addProduct("Product A", 19.99);
},
child: Text("Add Product A"),
),
),
SizedBox(width: 10),
keep(
ElevatedButton(
onPressed: () {
// Clear the cart
cartViewModel.value.clearCart();
},
child: Text("Clear Cart"),
),
),
],
),
],
);
},
),
),
);
}
}
Explanation
-
Model (
CartModel
):CartModel
is a class that holds the shopping cart data (items
andtotal
).- The
copyWith
method allows creating a new instance of the model with modified values while maintaining immutability.
-
ViewModel (
CartViewModel
):CartViewModel
extendsViewModelStateImpl<CartModel>
and handles business logic.- Instead of
state.copyWith
,value.copyWith
is used to access and update the current state. - The methods
addProduct
andclearCart
update the state usingupdateState
.
-
UI with
ReactiveBuilder
:ReactiveBuilder
observes theViewModel
state and rebuilds the UI when the state changes.- The
keep
function is used to prevent unnecessary button rebuilds, improving performance.
Using the Library Repository with ViewModelImpl
In this example, we are going to use the library's built-in repository to get and update the shopping cart data in the ViewModelImpl
.
1. Defining the Repository using the library
First, instead of creating a repository manually, we are going to use a repository provided by the library to interact with the data. Let's say you have a repository to handle cart-related data.
import 'package:reactive_notifier/reactive_notifier.dart';
class CartRepository extends RepositoryImpl<CartModel> {
// We simulate the loading of a shopping cart
Future<CartModel> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return CartModel(
items: ['Producto A', 'Producto B'],
total: 39.98,
);
}
// Method to add a product to the cart
Future<void> agregarProducto(CartModel carrito, String item, double price) async {
await Future.delayed(Duration(seconds: 1));
carrito.items.add(item);
carrito.total += price;
}
}
2. Cart Model (CartModel
)
The model remains the same:
class CartModel {
final List<String> items;
final double total;
CartModel({this.items = const [], this.total = 0.0});
// Method to clone the model with new values
CartModel copyWith({List<String>? items, double? total}) {
return CartModel(
items: items ?? this.items,
total: total ?? this.total,
);
}
}
3. ViewModelImpl
with the Repository
Now we are going to use the repository in the ViewModelImpl
to interact with the cart model. The ViewModelImpl
will leverage the repository to get data and make updates.
class CartViewModel extends ViewModelImpl<CartModel> {
final CartRepository repository;
CartViewModel(this.repository) : super(CartModel());
// Function to load the cart from the repository
Future<void> cargarCarrito() async {
try {
// We get the cart from the repository
final carrito = await repository.fetchData();
setState(carrito); // We update the status with the cart loaded
} catch (e) {
// Error handling
print("Error al cargar el carrito: $e");
}
}
// Function to add a product to the cart
Future<void> agregarProducto(String item, double price) async {
try {
await repository.agregarProducto(value, item, price);
// We update the status after adding the product
updateState(value.copyWith(items: value.items, total: value.total));
} catch (e) {
// Error handling
print("Error al agregar el producto: $e");
}
}
}
4. Repository Instance and ViewModelImpl
Here we create the repository instance and the ViewModelImpl
:
final cartViewModel = ReactiveNotifier<CartViewModel>((){
final cartRepository = CartRepository();
return CartViewModel(cartRepository);
});
5. Cart Status Widget
Finally, we are going to display the cart status in the UI using ReactiveBuilder
, which will automatically update when the status changes.
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Carrito de Compras")),
body: Center(
child: ReactiveBuilder<CartViewModel>(
valueListenable: cartViewModel.value,
builder: (context, viewModel, keep) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (viewModel.items.isEmpty)
keep(Text("Loading cart...")),
if (viewModel.items.isNotEmpty) ...[
keep(Text("Products in cart:")),
...viewModel.items.map((item) => Text(item)).toList(),
keep(const SizedBox(height: 20)),
Text("Total: \$${viewModel.total.toStringAsFixed(2)}"),
keep(const SizedBox(height: 20)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
keep(
ElevatedButton(
onPressed: () {
// Add a new product
cartViewModel.value.agregarProducto("Producto C", 29.99);
},
child: Text("Agregar Producto C"),
),
),
keep(const SizedBox(width: 10)),
keep(
ElevatedButton(
onPressed: () {
// Empty cart
cartViewModel.value.setState(CartModel());
},
child: Text("Vaciar Carrito"),
),
),
],
),
],
],
);
},
),
),
);
}
}
Explanation of the Example
- Repository (
CartRepository
):
- The repository extends
RepositoryImpl<CartModel>
, allowing you to interact with the cart data model. - The
fetchData
function simulates fetching the cart data, andaddProduct
simulates adding products to the cart.
- Model (
CartModel
):
CartModel
contains the cart data (items and total).- The
copyWith
method is used to create a new instance with modified values, maintaining immutability.
ViewModelImpl
(CartViewModel
):
CartViewModel
extendsViewModelImpl<CartModel>
, allowing you to handle the cart business logic.- The
loadCart
andaddProduct
methods interact with the repository and update the state of the cart.
- UI with
ReactiveBuilder
:
ReactiveBuilder
observes the state of theViewModel
and updates the UI automatically when the state changes.keep
is used to avoid unnecessary button rebuilds.
Documentation for related
in ReactiveNotifier
The related
attribute in ReactiveNotifier
allows you to efficiently manage interdependent states. They can be used in different ways depending on the structure and complexity of the state you need to handle.
Types of related
Usage
- Direct Relationship between Simple Notifiers
- This is the case where you have multiple independent
ReactiveNotifier
s (of simple type likeint
,String
,bool
, etc.) and you want any change in one of these notifiers to trigger an update in aReactiveBuilder
that is watched by a combinedReactiveNotifier
.
- Relationship between a Main
ReactiveNotifier
and Other Complementary Notifiers
- In this approach, a main
ReactiveNotifier
handles a complex class (e.g.UserInfo
), and other notifiers complement this state, such asSettings
. The companion states are managed separately, but are related to the main state viarelated
. Changes to any of the related notifiers cause theReactiveBuilder
to be updated.
1. Direct Relationship between Simple Notifiers
In this approach, you have several simple ReactiveNotifier
s, and you use them together to notify state changes when any of these notifiers changes. The ReactiveNotifier
s are related to each other using the related
attribute, and you see a combined ReactiveBuilder
.
Example
final timeHoursNotifier = ReactiveNotifier<int>(() => 0);
final routeNotifier = ReactiveNotifier<String>(() => '');
final statusNotifier = ReactiveNotifier<bool>(() => false);
// A combined ReactiveNotifier that watches for changes in all three notifiers
final combinedNotifier = ReactiveNotifier(
() {},
related: [timeHoursNotifier, routeNotifier, statusNotifier],
);
- Explanation:
Here,
combinedNotifier
is aReactiveNotifier
that updates when any of the three notifiers (timeHoursNotifier
,routeNotifier
,statusNotifier
) changes. This is useful when you have several simple states and you want them all to be connected to trigger an update in the UI together.
Using with ReactiveBuilder
ReactiveBuilder.notifier(
notifier: combinedNotifier,
builder: (context, _, keep) {
return Column(
children: [
Text("Horas: ${timeHoursNotifier.value}"),
Text("Ruta: ${routeNotifier.value}"),
Text("Estado: ${statusNotifier.value ? 'Activo' : 'Inactivo'}"),
],
);
},
);
- Explanation:
ReactiveBuilder
watches thecombinedNotifier
. Since the related notifiers are configured, any changes totimeHoursNotifier
,routeNotifier
, orstatusNotifier
will automatically update the UI.
2. Relationship between a Main ReactiveNotifier
and Other Complementary Notifiers
In this approach, you have a main ReactiveNotifier
that handles a more complex class, such as a UserInfo
object, and other complementary ReactiveNotifier
s are related through related
. These complementary notifiers do not need to be declared inside the main object class, but are integrated with it through the related
attribute.
Example: UserInfo
with Settings
Imagine that we have a UserInfo
class that represents a user's information, and a Settings
class that contains complementary settings. The notifiers for these states are related to each other so that any change in Settings
or UserInfo
triggers a global update.
class UserInfo {
final String name;
final int age;
UserInfo({required this.name, required this.age});
// Constructor for default values
UserInfo.empty() : name = '', age = 0;
// Method to clone with new values
UserInfo copyWith({String? name, int? age}) {
return UserInfo(
name: name ?? this.name,
age: age ?? this.age,
);
}
}
// Complementary notifiers for configurations
final settingsNotifier = ReactiveNotifier<String>(() => 'Dark Mode');
final notificationsEnabledNotifier = ReactiveNotifier<bool>(() => true);
// Combined ReactiveNotifier that watches all related notifiers
final userStateNotifier = ReactiveNotifier<UserInfo>(
() => UserInfo.empty(),
related: [settingsNotifier, notificationsEnabledNotifier],
);
- Explanation:
In this example,
userStateNotifier
is the mainReactiveNotifier
that handles the state ofUserInfo
.settingsNotifier
andnotificationsEnabledNotifier
are companion notifiers that handle user settings such as dark mode and enabling notifications. While they are not declared withinUserInfo
, they are related to it viarelated
.
Using with ReactiveBuilder
ReactiveBuilder<UserInfo>(
valueListenable: userStateNotifier.value,
builder: (context, userInfo, keep) {
return Column(
children: [
Text("Usuario: ${userInfo.name}, Edad: ${userInfo.age}"),
Text("ConfiguraciΓ³n: ${settingsNotifier.value}"),
Text("Notificaciones: ${notificationsEnabledNotifier.value ? 'Habilitadas' : 'Deshabilitadas'}"),
],
);
},
);
- Explanation:
ReactiveBuilder
watchesuserStateNotifier.value
(the user state). It also watches the related notifiers (settings and notifications). This means that any change to any of these notifiers will trigger an update in the UI.
Usage with ReactiveBuilder.notifier
ReactiveBuilder.notifier(
notifier: userStateNotifier,
builder: (context, userInfo, keep) {
return Column(
children: [
ElevatedButton(
onPressed: () {
userStateNotifier.updateState(userInfo.copyWith(name: "Nuevo Nombre"));
},
child: Text("Actualizar Nombre"),
),
],
);
},
);
- Explanation:
We use
ReactiveBuilder.notifier
to directly observe and update theuserStateNotifier
. When the user's name or any other value changes, it is automatically updated in the UI.
Advantages of Using related
in ReactiveNotifier
-
Flexibility: You can relate simple and complex notifiers without the need to involve additional classes. This is useful for handling states that depend on multiple values ββwithout overcomplicating the structure.
-
Optimization: When related notifiers change, the UI is automatically updated without the need to manually manage dependencies. This streamlines the workflow and improves the performance of the application.
-
Scalability: As your application grows, you can easily add more notifiers and relate them without modifying the existing logic, simply by extending the list of
related
. -
Simplicity: You can easily handle complex states using
related
, keeping everything decoupled and clean without the need to wrap everything in a single ViewModel.
Accessing Related States within a ReactiveBuilder
When you have multiple related ReactiveNotifier
s, you can access the states in a number of ways within a ReactiveBuilder
. Here I will explain the different ways to do this:
- Directly accessing the related
ReactiveNotifier
s. - Using the
from<T>()
method to access the related states within aReactiveNotifier
. - Using
keyNotifier
to access a specificReactiveNotifier
.
General Example: Relating Notifiers and Accessing their States
First, we will define the individual notifiers and then create a relationship between them using the related
attribute within a parent ReactiveNotifier
.
Defining Notifiers and Relationships
final userState = ReactiveNotifier<UserState>(() => UserState());
final cartState = ReactiveNotifier<CartState>(() => CartState());
final settingsState = ReactiveNotifier<SettingsState>(() => SettingsState());
final appState = ReactiveNotifier<AppState>(
() => AppState(),
related: [userState, cartState, settingsState],
);
userState
,cartState
andsettingsState
are individual states, andappState
is the mainReactiveNotifier
that is related to them. This means that when any of the related states change,appState
will be automatically updated.
1. Accessing Related States Directly
In a ReactiveBuilder
, you can directly access related notifiers without using additional methods like from<T>()
or keyNotifier
. You simply use the notifiers directly inside the builder
.
Usage in Direct ReactiveBuilder
class AppDashboard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveBuilder<AppState>(
valueListenable: appState,
builder: (context, state, keep) {
final user = userState.value;
final cart = cartState.value;
final settings = settingsState.value;
return Column(
children: [
Text('Welcome ${user.name}'),
Text('Cart Items: ${cart.items.length}'),
Text('Settings: ${settings.theme}'),
if (user.isLoggedIn) keep(const UserProfile())
],
);
},
);
}
}
- Explanation:
- Here, we directly access the values ββof
userState
,cartState
, andsettingsState
using.value
. - Pros: It's a quick and straightforward way to access the values ββif you don't need to perform any extra logic on them.
- Cons: If you need to access a specific value of a related
ReactiveNotifier
and it's not directly in thebuilder
, you might need something more organized, like usingkeyNotifier
or thefrom<T>()
method.
2. Using the from<T>()
Method
The from<T>()
method is used to access a related state within a ReactiveNotifier
. This method allows you to access a specific state more explicitly, especially if you need to get the value of a related state without directly accessing the ReactiveNotifier
.
Usage with from<T>()
class AppDashboard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveBuilder<AppState>(
valueListenable: appState,
builder: (context, state, keep) {
final user = appState.from<UserState>();
final cart = appState.from<CartState>();
final settings = appState.from<SettingsState>();
return Column(
children: [
Text('Welcome ${user.name}'),
Text('Cart Items: ${cart.items.length}'),
Text('Settings: ${settings.theme}'),
if (user.isLoggedIn) keep(const UserProfile())
],
);
},
);
}
}
- Explanation:
- We use
appState.from<UserState>()
to access the related user state. Similarly, we usecartState.keyNotifier
to accessCartState
using itskeyNotifier
. - Pros:
from<T>()
is useful when you have multiple related states and want to extract a value from a specific one more explicitly. - Cons: Although it is more organized, it can add complexity if you only need to access one or two states in a simple way.
3. Using keyNotifier
to Access Specific Notifiers
The keyNotifier
is useful when you want to access a related state that has a unique key within the related
relationship. This is especially useful when you have multiple notifiers of the same type (for example, multiple cartState
s) and you need to distinguish between them.
Using keyNotifier
class AppDashboard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveBuilder<AppState>(
valueListenable: appState,
builder: (context, state, keep) {
final user = appState.from<UserState>(userState.keyNotifier);
final cart = appState.from<CartState>(cartState.keyNotifier);
final settings = appState.from<SettingsState>(settingsState.keyNotifier);
return Column(
children: [
Text('Welcome ${user.name}'),
Text('Cart Items: ${cart.items.length}'),
Text('Settings: ${settings.theme}'),
if (user.isLoggedIn) keep(const UserProfile())
],
);
},
);
}
}
- Explanation:
appState.from<CartState>(cartState.keyNotifier)
accesses the cart state using itskeyNotifier
.- Pros: Using
keyNotifier
is useful when you have states of similar types or when you want to specify which instance of aReactiveNotifier
to use within a relationship. - Cons: If you only have one
ReactiveNotifier
of each type, this may be unnecessary, but in more complex scenarios with multiple notifiers of the same type, it's a great way to distinguish them.
Summary of Ways to Access Related States
- Direct Access to
ReactiveNotifier
:
- Simplest way:
userState.value or final user = userState.value;
- Ideal for simple, straightforward states.
- Using
from<T>()
:
- Explicit access to a related state:
final user = appState.from<UserState>();
- Ideal for handling more complex relationships between notifiers and extracting values ββfrom a specific state.
- Using
keyNotifier
:
- Access to a related state with a unique identifier:
final cart = appState.from<CartState>(cartState.keyNotifier);
- Ideal for handling notifiers of the same type and differentiating between them.
What to Avoid
// β NEVER: Nested related states
final cartState = ReactiveNotifier<CartState>(
() => CartState(),
related: [userState] // β Don't do this
);
// β NEVER: Chain of related states
final orderState = ReactiveNotifier<OrderState>(
() => OrderState(),
related: [cartState] // β Avoid relation chains
);
// β
CORRECT: Flat structure with single parent
final appState = ReactiveNotifier<AppState>(
() => AppState(),
related: [userState, cartState, orderState]
);
Async & Stream Support
Async Operations
class ProductViewModel extends AsyncViewModelImpl<List<Product>> {
@override
Future<List<Product>> fetchData() async {
return await repository.getProducts();
}
}
class ProductsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveAsyncBuilder<List<Product>>(
viewModel: productViewModel,
buildSuccess: (products) => ProductGrid(products),
buildLoading: () => const LoadingSpinner(),
buildError: (error, stack) => ErrorWidget(error),
buildInitial: () => const InitialView(),
);
}
}
Stream Handling
final messagesStream = ReactiveNotifier<Stream<Message>>(
() => messageRepository.getMessageStream()
);
class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ReactiveStreamBuilder<Message>(
streamNotifier: messagesStream,
buildData: (message) => MessageBubble(message),
buildLoading: () => const LoadingIndicator(),
buildError: (error) => ErrorMessage(error),
buildEmpty: () => const NoMessages(),
buildDone: () => const StreamComplete(),
);
}
}
Debugging System
ReactiveNotifier includes a comprehensive debugging system with detailed error messages:
Creation Tracking
π¦ Creating ReactiveNotifier<UserState>
π With related types: CartState, OrderState
Invalid Structure Detection
β οΈ Invalid Reference Structure Detected!
ββββββββββββββββββββββββββββββ
Current Notifier: CartState
Key: cart_key
Problem: Attempting to create a notifier with an existing key
Solution: Ensure unique keys for each notifier
Location: package:my_app/cart/cart_state.dart:42
Performance Monitoring
β οΈ Notification Overflow Detected!
ββββββββββββββββββββββββββββββ
Notifier: CartState
50 notifications in 500ms
β Problem: Excessive updates detected
β
Solution: Review update logic and consider debouncing
And more...
Best Practices
State Declaration
- Declare ReactiveNotifier instances globally or as static mixin members
- Never create instances inside widgets
- Use mixins for better organization of related states
Performance Optimization
- Use
keep
for static content - Maintain flat state hierarchy
- Use keyNotifier for specific state access
- Avoid unnecessary rebuilds
Architecture Guidelines
- Follow MVVM pattern
- Utilize Repository/Service patterns
- Let ViewModels initialize automatically
- Keep state updates context-independent
Related States
- Maintain flat relationships
- Avoid circular dependencies
- Use type-safe access
- Keep state updates predictable
Coming Soon: Real-Time State Inspector π
We're developing a powerful visual debugging interface that will revolutionize how you debug and monitor ReactiveNotifier states:
Features in Development
- π Real-time state visualization
- π Live update tracking
- π Performance metrics
- πΈοΈ Interactive dependency graph
- β±οΈ Update timeline
- π Deep state inspection
- π± DevTools integration
This tool will help you:
- Understand state flow in real-time
- Identify performance bottlenecks
- Debug complex state relationships
- Monitor rebuild patterns
- Optimize your application
- Develop more efficiently
Examples
Check out our example app for more comprehensive examples and use cases.
Contributing
We love contributions! Please read our Contributing Guide first.
- Fork it
- Create your feature branch (
git checkout -b feature/amazing
) - Commit your changes (
git commit -am 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing
) - Create a new Pull Request
Support
- π Star the repo to show support
- π Create an issue for bugs
- π‘ Submit feature requests through issues
- π Contribute to the documentation
License
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ by JhonaCode
Libraries
- reactive_notifier
- A library for managing reactive state in Flutter applications.