Bloc Dependency Manager
Bloc Dependency Manager is a centralized dependency management package designed to simplify the handling, registration, and lifecycle management of Blocs/Cubits in Dart and Flutter applications. It provides an organized way to manage BLoC dependencies, add custom state listeners, and handle event dispatching through a core state management system.
This package aims to help developers avoid repetitive setup and cleanup tasks, minimize dependency conflicts, and create scalable and maintainable architectures in Flutter applications.
How It Works
The Bloc Dependency Manager package manages Blocs and their state communications through a central BlocManager
, using several key classes and design patterns to simplify dependency handling, state changes, and event-driven communication.
Architecture Overview
BlocManager
- A singleton class that provides centralized BLoC registration, and listener management.StateDispatcher
- Manages and dispatches state emitters, which are used to handle and distribute BLoC state changes.BaseStateEmitter
- An abstract base class for creating custom state emitters that define how different states should be handled for specific listeners.BaseStateListener
- An interface to define actions for listeners to respond to BLoC state changes.
Core Design Patterns
- Singleton Pattern: Ensures only one
BlocManager
instance exists across the app. - Dependency Injection: Blocs are registered with
BlocManager
and can be lazily or eagerly instantiated. - Observer Pattern:
BlocManager
notifies listeners of state changes, allowing them to respond independently. - Strategy Pattern:
StateEmitters
handle different strategies for managing state changes.
Key Features
- Centralized BLoC Management: Register and manage all Blocs through a single BlocManager instance.
- State Emission and Custom Listeners: Attach state listeners and create custom emitters to manage and track state changes across Blocs.
- Automated Resource Disposal: Dispose of Blocs and related listeners automatically to prevent memory leaks.
- Seamless Integration with BLoC Library: Works smoothly with the bloc package to manage state across applications.
How It Works
The Bloc Dependency Manager operates through several primary components:
- BlocManager: A singleton class that manages the lifecycle of Blocs, storing them in a repository and handling their registration and disposal.
- BaseStateEmitter: A base class for custom state emitters, which trigger specific actions when a BLoC's state changes.
- StateDispatcher: Registers and manages custom emitters, allowing multiple state listeners to react to changes in the central BlocManager.
- BaseStateListener: An abstract class for defining behaviors for state listeners, allowing you to implement custom response methods to BLoC state changes.
Getting Started
Here's a complete walkthrough to demonstrate how to set up and use the Bloc Dependency Manager.
1. Create a BLoC
Define a BLoC (CounterBloc
) that will emit states based on user actions:
enum CounterState { increment, decrement, reset }
class CounterBloc extends Cubit<CounterState> {
CounterBloc() : super(CounterState.reset);
void reset() {
emit(CounterState.reset);
}
void increment() {
emit(CounterState.increment);
}
void decrement() {
emit(CounterState.decrement);
}
}
In this example, the CounterBloc
emits CounterState.increment
, CounterState.decrement
, or CounterState.reset
based on the method called.
2. Define a State Listener Interface
Create a listener interface for responding to changes in the CounterBloc
state.
abstract class CounterStateListener extends BaseStateListener {
void onCounterStateReset();
void onCounterStateChange(CounterState state);
}
Here, CounterStateListener
defines the methods that will be triggered when specific states are emitted by the CounterBloc
.
3. Implement a Custom State Emitter
Use a CounterStateEmitter
to broadcast state changes to listeners:
class CounterStateEmitter
extends BaseStateEmitter<CounterStateListener, CounterBloc> {
CounterStateEmitter(super.blocManager);
@override
void handleStates({
required CounterStateListener stateListener,
required Object? state,
}) =>
switch (state) {
CounterState.reset => stateListener.onCounterStateReset(),
CounterState.increment =>
stateListener.onCounterStateChange(CounterState.increment),
CounterState.decrement =>
stateListener.onCounterStateChange(CounterState.decrement),
_ => throw UnimplementedError(),
};
}
The CounterStateEmitter
listens to CounterBloc
changes and invokes methods in CounterStateListener
depending on the current state of CounterBloc
.
4. Create a Logger Bloc for Tracking Changes
Implement a LoggerBloc
that listens to CounterStateListener
and logs state changes.
class LoggerBloc extends Cubit<String> implements CounterStateListener {
LoggerBloc() : super('');
@override
void onCounterStateReset() {
emit('Counter state has been reset.');
}
@override
void onCounterStateChange(CounterState counterState) {
emit('Counter state changed to $counterState.');
}
}
In LoggerBloc
, the state is emitted as a log message whenever the counter state changes.
5. Set up and Use BlocManager
Register LoggerBloc
, CounterBloc
, and the custom CounterStateEmitter
in BlocManager
and dispatch some events to test the setup.
Future<void> main() async {
// Register all the blocs.
BlocManager().register(LoggerBloc());
BlocManager().register(CounterBloc());
// Register the state emitter for the [CounterBloc].
StateDispatcher(BlocManager()).register<CounterBloc, CounterStateEmitter>(
(BaseBlocManager blocManager) =>
CounterStateEmitter(blocManager as BlocManager),
);
// Resolve the [LoggerBloc] and listen to its state changes.
BlocManager().resolve<LoggerBloc>().stream.listen(print);
// Resolve the [CounterBloc] and dispatch some events.
BlocManager().resolve<CounterBloc>().decrement();
await Future<void>.delayed(const Duration(seconds: 1));
BlocManager().resolve<CounterBloc>().increment();
await Future<void>.delayed(const Duration(seconds: 1));
BlocManager().resolve<CounterBloc>().reset();
await Future<void>.delayed(const Duration(seconds: 1));
// Dispose [BlocManager] to clean up resources.
await BlocManager().dispose<LoggerBloc>();
await BlocManager().dispose<CounterBloc>();
print('All blocs disposed.');
}
Explanation of the Flow
- Bloc Registration: Both
CounterBloc
andLoggerBloc
are registered withBlocManager
for centralized access. - State Emitter Registration: The
CounterStateEmitter
is registered to handle state emissions forCounterBloc
by usingStateDispatcher
. - Event Dispatch: Actions are dispatched to
CounterBloc
(increment, decrement, reset), causing state changes. - State Listening:
LoggerBloc
, registered as aCounterStateListener
, responds to each state change and logs the output. - Cleanup: Finally,
BlocManager().dispose()
is called to clean up all registered blocs and listeners.
API Reference
BlocManager
The main singleton class for managing the lifecycle of Blocs and providing centralized access to registered instances.
Methods
-
register<B>()
- Registers a BLoC instance of type
B
. - Parameters:
B bloc
: The BLoC instance to register.String key
(optional): The identifier for the BLoC; defaults todefaultKey
.
- Returns: The registered BLoC instance.
- Registers a BLoC instance of type
-
resolve<B>()
- Retrieves a registered BLoC by its type and optional key.
- Parameters:
String key
(optional): Identifier for the BLoC to resolve; defaults todefaultKey
.
- Returns: The resolved BLoC instance.
- Throws:
BlocManagerException
if the BLoC is not registered.
-
isBlocRegistered<B>()
- Checks if a BLoC of a certain type and key is registered.
- Parameters:
String key
: The identifier for the BLoC.
- Returns:
true
if the BLoC is registered,false
otherwise.
-
addListener<B>()
- Adds a listener to a registered BLoC to listen for state changes.
- Parameters:
String listenerKey
: Identifier for the listener.BlocManagerListenerHandler handler
: Callback to execute when the BLoC's state changes.String key
(optional): Identifier for the BLoC; defaults todefaultKey
.
-
hasListener<B>()
- Checks if a listener with a specific key exists for a registered BLoC.
- Parameters:
String key
: The identifier for the listener.
- Returns:
true
if the listener exists,false
otherwise.
-
removeListener<B>()
- Removes a listener associated with a specific BLoC and key.
- Parameters:
String key
(optional): Identifier for the listener; defaults todefaultKey
.
- Returns: A
Future
that completes when the listener is removed.
-
registerStateEmitter()
- Registers a custom state emitter that listens for and processes specific state changes.
- Parameters:
GenericStateEmitter stateEmitter
: The state emitter to register.
-
emitCoreStates<E>()
- Dispatches states to all registered state emitters for a specific BLoC.
- Parameters:
GenericBloc bloc
: The BLoC for which the state is emitted.Object? state
: The state to emit to listeners.
-
dispose<B>()
- Disposes a registered BLoC and removes any associated listeners.
- Parameters:
String key
(optional): Identifier for the BLoC to dispose; defaults todefaultKey
.
- Returns: A
Future
that completes when the BLoC is disposed.
StateDispatcher
The StateDispatcher
class is a helper that registers state emitters for specified Blocs, allowing listeners to be triggered on BLoC state changes.
Methods
register<B, E>()
- Registers a state emitter of type
E
for a BLoC of typeB
, enabling state emission to be managed centrally. - Parameters:
StateEmitterBuilder stateEmitterBuilder
: A builder function to initialize the state emitter with theBlocManager
.
- Registers a state emitter of type
BaseStateEmitter
The BaseStateEmitter
is an abstract class for creating custom state emitters that broadcast state changes to listeners. It’s designed to be extended to implement specific behaviors based on BLoC state changes.
Methods
-
handleStates()
- Defines how states are handled for a specific state listener.
- Parameters:
BaseStateListener stateListener
: The listener that will respond to state changes.Object state
: The state being emitted by the BLoC.
-
call()
- Triggers the emission of the current state to the listener, with optional handling for a custom state.
- Parameters:
BaseStateListener stateListener
: The listener to receive the state.Object? state
: The state to emit. Defaults to the BLoC’s current state if not provided.
BaseStateListener
An abstract class for creating listener interfaces to respond to specific state changes in Blocs. Implementing classes define the actions that occur in response to BLoC state changes.
Methods
- Implement custom methods in classes that extend
BaseStateListener
to define responses to specific states (e.g.,onCounterStateReset
,onCounterStateChange
in aCounterStateListener
implementation).
Contributing
Contributions to the Bloc Dependency Manager package are welcome. Feel free to submit issues, feature requests, or pull requests to help improve the package and make it more useful for the Flutter community.