संबंधित सोर्स रिपॉज़िटरी के मूल README का स्नैपशॉट। नवीनतम सामग्री के लिए रिपॉज़िटरी देखें।
SyncX
Flutter state management made easy: SyncX is a lightweight, flexible state management solution for Flutter. It provides notifiers, builders, and consumers for reactive UI updates with minimal boilerplate. SyncX is designed for developers who want a simple, composable, and powerful way to manage state in their Flutter apps.
Note: The implementation and API of SyncX are inspired by popular state management solutions such as bloc, provider, and riverpod. Naming conventions and patterns are chosen to be developer-friendly, especially for those migrating from or familiar with these libraries.
Table of Contents
Why SyncX?
- Simple and familiar API for Flutter state management.
- Reactive UI updates with rebuild control: Widgets rebuild on state changes, with control over when they update.
- Notifier pattern: Cleanly separates business logic from UI code.
- Effortless async state handling: Easily manage loading, data, and error states.
- Minimal boilerplate: Quick to set up and easy to integrate into any Flutter project.
Features
- 🔄 Notifier-based state management: Simple, extendable notifiers for your app's state.
- 🏗️ Builder widgets: Easily rebuild UI in response to state changes.
- 👂 Listener widgets: React to state changes with side effects.
- 🪶 Minimal boilerplate: Focus on your app logic, not on wiring up state.
- ⚡ Async state support: Built-in support for loading, data, and error states in async flows.
Core Concepts & Usage
1. Create a Notifier
A Notifier holds and updates your state:
class CounterNotifier extends Notifier<int> {
CounterNotifier() : super(0);
@override
void onInit(){
}
void increment() => setState(state + 1);
}
For async state (loading/data/error):
class GreetingAsyncNotifier extends AsyncNotifier<String> {
GreetingAsyncNotifier() : super();
@override
Future<AsyncState<String>> onInit() async {
await Future.delayed(const Duration(seconds: 2));
final bool isSuccess = true;
if (isSuccess) {
return const AsyncState.data('Hello from AsyncNotifier!');
}
return const AsyncState.error('Failed to load greeting');
}
Future<void> updateState() async {
setState(state.toLoading());
await Future.delayed(const Duration(seconds: 2));
final bool isSuccess = true;
if (isSuccess) {
return setState(state.toData('Success'));
}
return setState(
state.toError('..errorObject', message: 'Network call failed'),
);
}
}
2. Register the Notifier
Register your notifier at the root of your widget tree:
NotifierRegister(
create: (context) => CounterNotifier(),
child: MyApp(),
)
You can nest registers for multiple notifiers:
NotifierRegister(
create: (context) => CounterNotifier(),
child: NotifierRegister(
create: (context) => GreetingAsyncNotifier(),
child: MyApp(),
),
)
3. Consume State with Builders & Listeners
Rebuild UI on State Change:
NotifierBuilder<CounterNotifier, int>(
builder: (context, count) => Text('Count: $count'),
)
Control rebuilds:
NotifierBuilder<CounterNotifier, int>(
buildWhen: (prev, curr) => prev != curr,
builder: (context, count) => Text('Count: $count'),
)
Listen for Side Effects:
NotifierListener<CounterNotifier, int>(
listenWhen: (prev, curr) => curr % 2 == 0,
listener: (count) => print('Count is even: $count'),
child: MyWidget(),
)
Combine Build and Listen:
NotifierConsumer<CounterNotifier, int>(
builder: (context, count) => Text('Count: $count'),
listener: (count) => print('Count changed: $count'),
)
Async State Handling:
SyncX provides specialized widgets for handling asynchronous state:
AsyncNotifierBuilder – a wrapper over NotifierBuilder for async state, providing a convenient API for loading, data, and error UI.
AsyncNotifierConsumer – combines building and listening for side effects in async flows.
AsyncNotifierListener – listens for async state changes and triggers side effects without rebuilding the UI.
Direct usage with builder:
AsyncNotifierBuilder<GreetingAsyncNotifier, String>(
builder: (context, state) {
return state.when(
loading: () => const CircularProgressIndicator(),
data: (greeting) => Text(greeting),
error: (error) => Text('Error: \\${error.message}'),
);
},
)
Convenient usage with .withData:
AsyncNotifierBuilder<GreetingAsyncNotifier, String>.withData(
loadingBuilder: () => CircularProgressIndicator(),
dataBuilder: (greeting) => Text(greeting),
errorBuilder: (error) => Text('Error: \\${error.message}'),
)
You can use AsyncNotifierConsumer and AsyncNotifierListener similarly to handle both UI and side effects for async state.
4. Invoking Notifier Methods
To invoke a method inside your notifier (for example, to increment the counter), use the context extension or provider pattern in your widget. Here’s how you can call a notifier method from a button press:
ElevatedButton(
onPressed: () => context.read<CounterNotifier>().increment(),
child: const Text('Increment'),
)
This will call the increment method on your CounterNotifier and update the state accordingly.
Bad Practices / Don’ts
-
Don’t: Do not call the notifier’s onInit manually from builder, consumer, or listener onInit callbacks. This will result in duplicate initialization and unexpected behavior. The onInit method inside your notifier will be called automatically once the notifier instance is created. You do not need to call it yourself.
Incorrect usage example:
NotifierBuilder<Notifier, State>(
onInit: (notifier) => notifier.onInit(),
onInit: (notifier) => notifier.doSomethingElse(),
builder: (context, state) => Text('State: $state'),
)
Usage Patterns
- Rebuild UI on State Change: Use
NotifierBuilder with buildWhen for fine-grained rebuild control.
- Listen for Side Effects: Use
NotifierListener with listenWhen for side-effect logic.
- Combine Build and Listen: Use
NotifierConsumer for both UI and side effects.
- Async State Handling: Use
AsyncNotifier and AsyncState for loading, data, and error flows.
Future Plans
- Built-in stream support in notifiers for easy loading and error state management.
- Top-level observer support to monitor state changes across the app.
- Migrate dependency injection (DI) from provider to a custom DI implementation for more flexibility and control.
Contributing
Contributions are welcome! Please open issues or pull requests on GitHub.
License
This project is licensed under the MIT License.
If you have questions or need help, please open an issue or start a discussion on GitHub.