bloc_small 1.0.0 bloc_small: ^1.0.0 copied to clipboard
An easy-to-use Flutter package offering a streamlined BLoC pattern implementation for intuitive and efficient state management in Flutter apps.
bloc_small #
A lightweight and simplified BLoC (Business Logic Component) library for Flutter, built on top of the flutter_bloc
package. This library is designed to make state management easier and more intuitive, while leveraging the power and
flexibility of flutter_bloc
. bloc_small
aims to simplify the BLoC pattern implementation, maintaining its core
benefits of separating business logic from presentation code, and providing additional utilities and abstractions for
common use cases.
Table of Contents #
- Features
- Installation
- Core Concepts
- Basic Usage
- Advanced Usage
- Best Practices
- API Reference
- Contributing
- License
Features #
- Simplified BLoC pattern implementation using
flutter_bloc
- Easy-to-use reactive programming with Dart streams
- Automatic resource management and disposal
- Integration with GetIt for dependency injection
- Support for loading states and error handling
- Streamlined state updates and event handling
- Built-in support for asynchronous operations
- Seamless integration with Freezed for immutable state and event classes
Installation #
Add bloc_small
to your pubspec.yaml
file:
dependencies:
bloc_small:
flutter_bloc:
injectable:
freezed:
get_it:
dev_dependencies:
injectable_generator:
build_runner:
freezed_annotation:
Then run:
After adding or modifying any code that uses Freezed or Injectable, run the build runner:
flutter pub run build_runner build --delete-conflicting-outputs
Note: Remember to run the build runner command every time you make changes to files that use Freezed or Injectable annotations. This generates the necessary code for your BLoCs, events, and states.
Core Concepts #
MainBloc #
The MainBloc
class is the foundation of your BLoCs. It extends MainBlocDelegate
and provides a structure for
handling events and emitting states.
MainBlocEvent #
All events in your BLoCs should extend MainBlocEvent
. These events trigger state changes in your BLoC.
MainBlocState #
States in your BLoCs should extend MainBlocState
. These represent the current state of your application or a specific
feature.
CommonBloc #
The CommonBloc
is used for managing common functionalities across your app, such as loading states.
Basic Usage #
1. Define your BLoC #
Create a class that extends MainBloc
:
@injectable
class CountBloc extends MainBloc<CountEvent, CountState> {
CountBloc() : super(const CountState.initial()) {
on<Increment>(_onIncrementCounter);
on<Decrement>(_onDecrementCounter);
}
Future<void> _onIncrementCounter(Increment event, Emitter<CountState> emit) async {
await blocCatch(actions: () async {
await Future.delayed(Duration(seconds: 2));
emit(state.copyWith(count: state.count + 1));
});
}
void _onDecrementCounter(Decrement event, Emitter<CountState> emit) {
if (state.count > 0) emit(state.copyWith(count: state.count - 1));
}
}
2. Define Events and States with Freezed #
abstract class CountEvent extends MainBlocEvent {
const CountEvent._();
}
@freezed
class Increment extends CountEvent with _$Increment {
const factory Increment() = _Increment;
}
@freezed
class Decrement extends CountEvent with _$Decrement {
const factory Decrement() = _Decrement;
}
@freezed
class CountState extends MainBlocState with $CountState {
const factory CountState.initial({@Default(0) int count}) = Initial;
}
3. Set up Dependency Injection #
Use GetIt and Injectable for dependency injection:
final GetIt getIt = GetIt.instance;
@injectableInit
void configureInjectionApp() => getIt.init();
@module
abstract class RegisterModule {
@injectable
CommonBloc get commonBloc => CommonBloc();
}
Important: The
RegisterModule
class with theCommonBloc
singleton is essential. If you don't include this Dependency Injection setup, your app will encounter errors. TheCommonBloc
is used internally bybloc_small
for managing common functionalities like loading states across your app.
Make sure to call configureInjectionApp()
before running your app:
4. Create a StatefulWidget with BasePageState #
class MyHomePage extends StatefulWidget {
MyHomePage({required this.title});
final String title;
@override
MyHomePageState createState() => _MyHomePageState(getIt);
}
class MyHomePageState extends BasePageState<MyHomePage, CountBloc> {
MyHomePageState(GetIt getIt) : super(getIt);
@override
Widget buildPage(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: BlocBuilder<CountBloc, CountState>(
builder: (context, state) {
return Text(
'${state.count}',
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => bloc.add(Increment()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
FloatingActionButton(
onPressed: () => bloc.add(Decrement()),
tooltip: 'decrement',
child: Icon(Icons.remove),
),
],
),
);
}
}
5. Initialize the App #
In your main.dart
file:
void main() {
configureInjectionApp();
runApp(MyApp());
}
Advanced Usage #
Handling Loading States #
bloc_small
provides a convenient way to manage loading states and display loading indicators using the CommonBloc
and the buildLoadingOverlay
method.
Using buildLoadingOverlay
When using BasePageState
, you can easily add a loading overlay to your entire page:
class MyHomePageState extends BasePageState<MyHomePage, CountBloc> {
MyHomePageState(GetIt getIt) : super(getIt);
@override
Widget buildPage(BuildContext context) {
return buildLoadingOverlay(
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
BlocBuilder<CountBloc, CountState>(
builder: (context, state) {
return Text('${state.count}');
},
)
],
),
),
floatingActionButton: Wrap(
spacing: 5,
children: [
FloatingActionButton(
onPressed: () => bloc.add(Increment()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
FloatingActionButton(
onPressed: () => bloc.add(Decrement()),
tooltip: 'decrement',
child: Icon(Icons.remove),
),
],
),
),
);
}
}
The buildLoadingOverlay
method wraps your page content and automatically displays a loading indicator when the loading
state is active.
Customizing the Loading Overlay
You can customize the loading overlay by providing a loadingWidget
and specifying a loadingKey
:
buildLoadingOverlay(
child: YourPageContent(),
loadingWidget: YourCustomLoadingWidget(),
loadingKey:'customLoadingKey'
)
Activating the Loading State
To show or hide the loading overlay, use the showLoading
and hideLoading
methods in your BLoC:
class YourBloc extends MainBloc<YourEvent, YourState> {
Future<void> someAsyncOperation() async {
showLoading(); // or showLoading(key: 'customLoadingKey');
try {
// Perform async operation
} finally {
hideLoading(); // or hideLoading(key: 'customLoadingKey');
}
}
}
This approach provides a clean and consistent way to handle loading states across your application, with the flexibility to use global or component-specific loading indicators.
Error Handling #
Use the blocCatch
method in your BLoC to handle errors:
await blocCatch(
actions: () async {
// Your async logic here
throw Exception('Something went wrong');
},
onError: (error) {
// Handle the error
print('Error occurred: $error');
}
);
Using ReactiveSubject #
Note: ReactiveSubject uses RxDart internally. Make sure you have
rxdart
in yourpubspec.yaml
dependencies.
ReactiveSubject provides a simple way to create and manage streams of data in your application. Here's a guide on how to use it:
- Creating a ReactiveSubject:
// For a single-subscription subject with an initial value
final counter = ReactiveSubject<int>(initialValue: 0);
// For a broadcast subject (multiple listeners)
final broadcastCounter = ReactiveSubject<int>.broadcast(initialValue: 0);
- Adding values:
counter.add(1);
counter.add(2);
- Getting the current value:
print(counter.value); // Prints the latest value
- Listening to changes:
counter.stream.listen((value) {
print('Counter changed: $value');
});
- Using with Flutter widgets:
StreamBuilder<int>(
stream: counter.stream,
initialData: counter.value,
builder: (context, snapshot) {
return Text('Count: ${snapshot.data}');
},
)
- Disposing:
@override
void dispose() {
counter.dispose();
super.dispose();
}
ReactiveSubject simplifies working with streams in your application, providing a convenient way to manage and react to changing data. It's particularly useful in BLoCs for handling state changes and in widgets for building reactive UIs.
Best Practices #
- Keep your BLoCs focused on a single responsibility.
- Use meaningful names for your events and states.
- Leverage the
CommonBloc
for app-wide state management. - Always dispose of your BLoCs and ReactiveSubjects when they're no longer needed.
- Use
blocCatch
for consistent error handling across your app. - Prefer composition to inheritance when creating complex BLoCs.
- Utilize Freezed for creating immutable event and state classes.
- Take advantage of Freezed's union types and pattern matching for more expressive and type-safe event handling.
API Reference #
MainBloc #
MainBloc(initialState)
: Constructor for creating a new BLoC.blocCatch({required Future<void> Function() actions, Function(dynamic)? onError})
: Wrapper for handling errors in async operations.
MainBlocState #
Base class for all states in your BLoCs.
MainBlocEvent #
Base class for all events in your BLoCs.
CommonBloc #
add(SetComponentLoading)
: Set loading state for a component.state.isLoading(String key)
: Check if a component is in loading state.
ReactiveSubject #
ReactiveSubject
is a wrapper around RxDart's BehaviorSubject
or PublishSubject
, providing a simpler API for reactive programming in Dart.
ReactiveSubject({T? initialValue})
: Create a new ReactiveSubject (wraps BehaviorSubject).ReactiveSubject.broadcast({T? initialValue})
: Create a new broadcast ReactiveSubject (wraps PublishSubject).add(T value)
: Add a new value to the subject.value
: Get the current value.stream
: Get the stream of values.dispose()
: Dispose of the subject.
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
License #
This project is licensed under the MIT License - see the LICENSE file for details.