publisher_subscriber 0.0.2
publisher_subscriber: ^0.0.2 copied to clipboard
A simple, minimal, and opinionated state management library for Flutter.
Publisher/Subscriber #
A simple, minimal, and opinionated state management library for Flutter.
[Publisher/Subscriber Usage Example]
What is it? #
Publisher/Subscriber is a tool for managing your app's state. Think of it as a central place to hold information (like a user's name or a shopping cart's contents) that your app's widgets can use and automatically react to when it changes.
It's designed to be straightforward, with no complex setup, so you can focus on building your app.
Core Concepts #
There are just three key ideas to understand:
-
Publisher<T>: This is your state holder. It's a class that holds a value (like a number or a string) and tells other parts of your app when that value has been updated.// A simple publisher that holds a number. class Counter extends Publisher<int> { Counter() : super(0); // It starts with the value 0. void increment() => setState(state + 1); } -
PublisherRecipe: This is a "blueprint" that tells the system how to create yourPublisher. You don't use this directly, but create one usingPublisher.global()orPublisher.scoped(). -
Subscriber: This is a widget that listens to one or morePublishers. When aPublisherit's listening to changes, theSubscriberautomatically rebuilds its part of the UI to show the new information.
How to Use #
Step 1: Define a Recipe #
First, create a recipe for your Publisher. This recipe is like a blueprint: it's cheap to create and doesn't do anything until it's used. You can define all your recipes in one place.
// A recipe for a counter that lives as long as the app does.
final globalCounterRecipe = Publisher.global(() => Counter());
// A recipe for a counter that's created and destroyed automatically.
final scopedCounterRecipe = Publisher.scoped(() => Counter());
Step 2: Read State in the UI #
To use your state in a widget, wrap the part of the UI that needs the data in a Subscriber. Inside its builder, use context.read(recipe) to get your Publisher instance.
When you access .state, the Subscriber knows to listen for changes.
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Subscriber(
builder: (context) {
// Get the publisher instance using its recipe.
final counter = context.read(scopedCounterRecipe);
// Display the state. This widget will now rebuild
// automatically whenever the counter's state changes.
return Text('Count: ${counter.state}');
},
);
}
}
Global vs. Scoped: Which Should I Use? #
-
Use
Publisher.global()for app-wide state. This is for data that should never be lost as you navigate through the app.- Good Examples: User authentication status, theme settings, a shopping cart.
-
Use
Publisher.scoped()for temporary state. This is for data that's only relevant to a specific page or feature. It's automatically created when needed and cleaned up when no longer used, which is great for saving memory.- Good Examples: The text in a search bar, filters on a results page, the state of a single form.
A Note on Memory: Recipes vs. Instances #
It's safe to create all your recipes globally, even the "scoped" ones. Here's why:
- The Recipe (Blueprint): The recipe itself uses almost no memory. It's just a plan.
- The Instance (The House): The actual stateful
Publisherobject is only built and allocated in memory the first time it's requested by aSubscriber. - The Cleanup: When the last
Subscriberusing a scoped instance disappears, the instance is automatically destroyed and its memory is reclaimed.
This design ensures you only use memory for the state you are actively showing on screen.
Pro Tip: Keep Subscribers Small #
For the best performance, wrap only the specific widget that needs to rebuild, not your entire screen.
GOOD:
// Only the Text widget rebuilds when the counter changes.
Scaffold(
appBar: AppBar(
title: Subscriber(
builder: (context) {
final counter = context.read(globalCounterRecipe);
return Text('Count: ${counter.state}');
},
),
),
body: MyComplexWidgetTree(),
)
NOT-SO-GOOD:
// The entire Scaffold and all its children would rebuild.
Subscriber(
builder: (context) {
final counter = context.read(globalCounterRecipe);
return Scaffold(
appBar: AppBar(title: Text('Count: ${counter.state}')),
body: MyComplexWidgetTree(),
);
}
)
Advanced: Global Observer #
For debugging or logging, you can listen to all state changes for a certain type of Publisher from anywhere in your code.
void main() {
// This will print a message every time ANY Counter changes state.
GlobalPublisherObserver.on<Counter>((state) {
print('A Counter somewhere changed its state to: $state');
});
runApp(const MainApp());
}
Author #
This library is developed and maintained by p4-k4.