
Discover how Dart 3.13 primary constructors, 'this' constructor bodies, and constructor shorthands transform BlocSignal into the cleanest state management architecture in Flutter.
For years, one of the most common critiques of the BLoC pattern has been boilerplate.
Between declaring event classes, state hierarchies, constructor parameters, private fields, super-initializers, and event handler registries, you could easily write 50 lines of code before handling a single real-world user action.
With Dart 3.13, that all changes.
Dart 3.13 brings Primary Constructors, this constructor body blocks, and new/factory constructor shorthands. When combined with BlocSignal—the synchronous, signals-powered evolution of BLoC—the result is an ultra-concise, fully type-safe, and boilerplate-free state management workflow.
Let's explore how Dart 3.13 and BlocSignal fit together like hand in glove.
In classic BLoC, defining a family of immutable events or states meant writing repeated constructor signatures and field definitions for every subtype.
sealed class UserEvent {}
class UserFetchRequested extends UserEvent {
final String userId;
UserFetchRequested(this.userId);
}
class UserUpdated extends UserEvent {
final String name;
final int age;
UserUpdated({required this.name, required this.age});
}
class UserLoggedOut extends UserEvent {}
sealed class UserEvent {}
class UserFetchRequested(final String userId) extends UserEvent;
class UserUpdated({required final String name, required final int age}) extends UserEvent;
class UserLoggedOut() extends UserEvent;
A whole sealed hierarchy of events or states can now be declared in just a few clean, expressive lines without losing type safety or exhaustiveness checking in switch expressions.
CubitSignalIn CubitSignal, you typically inject repositories, API clients, or analytic trackers. In previous Dart versions, you had to declare each field, accept constructor arguments, and forward initial state to super.
With primary constructors, field declarations and super invocations live right in the class header.
class UserCubit extends CubitSignal<UserState> {
final UserRepository _repository;
final AnalyticsService _analytics;
UserCubit({
required UserRepository repository,
required AnalyticsService analytics,
UserState initial = const UserInitial(),
}) : _repository = repository,
_analytics = analytics,
super(initialState: initial);
Future<void> loadUser(String id) async {
emit(const UserLoading());
try {
final user = await _repository.fetchUser(id);
_analytics.track('user_loaded', {'id': id});
emit(UserSuccess(user));
} catch (e, st) {
onError(e, st);
emit(UserError(e.toString()));
}
}
}
class UserCubit(
final UserRepository repository,
final AnalyticsService analytics, {
final UserState initial = const UserInitial(),
}) extends CubitSignal<UserState>(initialState: initial) {
Future<void> loadUser(String id) async {
emit(const UserLoading());
try {
final user = await repository.fetchUser(id);
analytics.track('user_loaded', {'id': id});
emit(UserSuccess(user));
} catch (e, st) {
onError(e, st);
emit(UserError(e.toString()));
}
}
}
No field re-declarations. No duplicate parameter names. The dependencies are immediately available across all methods.
this Block in BlocSignalOne of the most powerful features in Dart 3.13 is the this constructor body syntax. When using primary constructors, constructor body logic (such as registering event handlers with on<E>() or asserting preconditions) is placed inside a this { ... } block in the class body.
class SearchBloc(
final SearchRepository repository, {
final SearchState initial = const SearchInitial(),
}) extends BlocSignal<SearchEvent, SearchState>(initialState: initial) {
// Dart 3.13 primary constructor body
this {
on<SearchQueryChanged>(
(event, emit) async {
if (event.query.trim().isEmpty) return emit(const SearchEmpty());
emit(const SearchLoading());
final results = await repository.search(event.query);
emit(SearchSuccess(results));
},
transformer: restartable(), // Zero-stream event concurrency!
);
}
}
The header cleanly declares the class contract, and the this block sets up the event pipeline.
createEffectBlocSignal includes createEffect, which automatically tracks signal dependencies and manages teardown on container disposal. With primary constructor parameters in scope, derived cubits can synchronously wire up upstream state containers in the this block:
class CartSummaryCubit(final CartBloc cartBloc)
extends CubitSignal<CartSummary>(initialState: const CartSummary.zero()) {
this {
// Automatically reacts to cartBloc.state signals synchronously:
createEffect(() {
final items = cartBloc.state.value.items;
final total = items.fold<double>(0, (sum, item) => sum + item.price);
emit(CartSummary(count: items.length, total: total));
});
}
}
new) for Testing & SeedingDart 3.13 also introduces constructor shorthands, allowing you to define secondary named constructors using new name() without repeating the class name:
class CounterCubit(var int count) extends CubitSignal<int>(initialState: count) {
// Named constructor shorthands:
new zero() : this(0);
new seeded(int initial) : this(initial);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
This makes testing variations, mock seeds, and default configurations concise and readable.
To take advantage of these features:
pubspec.yaml:environment:
sdk: ^3.13.0
dependencies:
bloc_signals: ^1.0.0
bloc_signals_flutter: ^1.0.0
analysis_options.yaml:include: package:very_good_analysis/analysis_options.yaml
linter:
rules:
- use_primary_constructors
- use_declaring_parameters
- unnecessary_type_name_in_constructor
- unnecessary_primary_constructor_body
By combining Dart 3.13 language features with BlocSignal, you get:
== de-duplication and fine-grained UI rebuilding.We'd love to hear your thoughts in the comments below:
Ready to build boilerplate-free reactive apps?
Check out the full documentation, benchmarks, and interactive examples at blocsignal.dev or star the open-source repository on GitHub!
opensourceHow to rescue abandoned open-source projects, modernize build systems, and generate multi-architecture Docker images (x86_64, ARM64) in a single afternoon with Antigravity.
aiPreface: It all started with a misunderstanding. I noticed a new page in the Gemini API...
awsA field report on serving Gemma 4 E2B under vLLM on AWS G5g — the only aarch64 + SM 7.5 hardware there is. No published build covers that combination, AWS quietly solves half of it, and the thing that actually blocks you is 64 KiB of shared memory.
discussEver since I joined the platform, I wanted to post about a topic I was really passionate about....
aiUpdate 08/15 0.2.0 Released github.com/deghosal-2026/agent-tooltrust · pip install agent-tooltrust...
geminiEvery week I get invited to more AI events than I can attend. Bond AI, Founders Bay, AI Collective,...
Workflows from the Neura Market marketplace related to this CoPilot resource