sectional_bloc_builder 0.0.2
sectional_bloc_builder: ^0.0.2 copied to clipboard
Section-aware BlocBuilder for Flutter that rebuilds only tagged UI sections.
sectional_bloc_builder #
Section-aware BlocBuilder
for Flutter that rebuilds only the UI parts you tag with section enums. Keeps widget trees fast and predictable on complex screens.
Why #
- Avoid whole-screen rebuilds when only a small part should update.
- Emit from your Bloc/Cubit who should rebuild via
UiSectionStatus
. - Consistent status model:
RequestStatus
+UiSectionStatus
. - Optional targeting for list items via
index
orobjectId
.
Compared to other approaches #
- Smaller cubits per subtree: clear ownership and isolation; fewer unintended rebuilds. However, it increases the number of blocs to wire, can duplicate state, and makes cross-subtree coordination harder. Prefer this when feature boundaries are sharp and subtrees are truly independent.
- BlocSelector / buildWhen (bloc built-ins, sometimes used as a “section builder”): no extra enums or library. You select the slice of state each widget needs and avoid rebuilds. Drawback: selection logic spreads across many widgets, coordinating multiple widgets for a single transition is repetitive, and item-scoped targeting (like
objectId
) is ad hoc. Prefer this on simple screens with a handful of consumers. - This library (section enums): the emitter says who should rebuild via
UiSectionStatus(sections: [...])
. It centralizes rebuild intent, keeps consumers simple, and adds optionalobjectId
targeting. It does require maintaining a small enum per screen and being consistent about settingsections
in emits.
Example with BlocSelector vs this package:
// BlocSelector
BlocSelector<ProfileCubit, ProfileState, String>(
selector: (s) => s.email,
builder: (_, email) => Text(email),
);
// SectionalBlocBuilder (emitter-driven targeting)
SectionalBlocBuilder<ProfileCubit, ProfileState, ProfileSection>(
sections: const [ProfileSection.details],
builder: (_, state) => Text(state.email),
);
Installation #
Add to your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^9.0.0
sectional_bloc_builder: ^0.0.1
Then import:
import 'package:sectional_bloc_builder/sectional_bloc_builder.dart';
Core Concepts #
RequestStatus
:initial | loading | success | failure
.UiSectionStatus<T extends Enum>
: carriessections: List<T>
,status
, plus optionalmessage
,index
, andobjectId
.SectionalState<T extends Enum>
: mix into your state to expose auiStatus
.SectionalBlocBuilder<B, S extends SectionalState<T>, T extends Enum>
: rebuilds only when emitted sections match the builder'ssections
(and optionalobjectId
).
Quick Start #
Define your sections, state, and cubit:
// 1) Sections
enum ProfileSection { header, details, submitButton }
// 2) State
class ProfileState extends SectionalState<ProfileSection> {
final String name;
final String email;
ProfileState({
required UiSectionStatus<ProfileSection> uiStatus,
required this.name,
required this.email,
}) : super(uiStatus: uiStatus);
ProfileState copyWith({
UiSectionStatus<ProfileSection>? uiStatus,
String? name,
String? email,
}) {
return ProfileState(
uiStatus: uiStatus ?? this.uiStatus,
name: name ?? this.name,
email: email ?? this.email,
);
}
}
// 3) Cubit
class ProfileCubit extends Cubit<ProfileState> {
ProfileCubit()
: super(ProfileState(
uiStatus: UiSectionStatus(
sections: const [], // empty = global
status: RequestStatus.initial,
),
name: 'Jane',
email: 'jane@example.com',
));
Future<void> updateEmail(String newEmail) async {
// Tell only the details section to show loading
emit(state.copyWith(
uiStatus: UiSectionStatus(
sections: const [ProfileSection.details],
status: RequestStatus.loading,
),
));
await Future<void>.delayed(const Duration(milliseconds: 600));
emit(state.copyWith(
email: newEmail,
uiStatus: UiSectionStatus(
sections: const [ProfileSection.details],
status: RequestStatus.success,
),
));
}
}
Build only what you need:
class ProfileView extends StatelessWidget {
const ProfileView({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Rebuilds when header is targeted
SectionalBlocBuilder<ProfileCubit, ProfileState, ProfileSection>(
sections: const [ProfileSection.header],
builder: (context, state) {
return Text('Hello, ${state.name}');
},
),
// Rebuilds when details is targeted
SectionalBlocBuilder<ProfileCubit, ProfileState, ProfileSection>(
sections: const [ProfileSection.details],
builder: (context, state) {
final loading = state.uiStatus.isLoading;
return Row(
children: [
Text(state.email),
if (loading) const Padding(
padding: EdgeInsets.only(left: 8),
child: SizedBox(width: 12, height: 12, child: CircularProgressIndicator(strokeWidth: 2)),
),
],
);
},
),
// Rebuilds when submitButton is targeted
SectionalBlocBuilder<ProfileCubit, ProfileState, ProfileSection>(
sections: const [ProfileSection.submitButton],
builder: (context, state) {
final busy = state.uiStatus.isLoading;
return ElevatedButton(
onPressed: busy
? null
: () => context.read<ProfileCubit>().updateEmail('new@example.com'),
child: Text(busy ? 'Saving...' : 'Save'),
);
},
),
],
);
}
}
Wrap with a provider as usual:
MaterialApp(
home: BlocProvider(
create: (_) => ProfileCubit(),
child: const Scaffold(body: Padding(
padding: EdgeInsets.all(16),
child: ProfileView(),
)),
),
);
Targeting Specific Items #
Two optional fields help scope updates within lists:
index
: good for stable, in-memory lists.objectId
: safer when the visual position can shift; match by id.
// Emitting from cubit for a specific item
emit(state.copyWith(
uiStatus: UiSectionStatus(
sections: const [ProfileSection.details],
status: RequestStatus.loading,
index: 3,
objectId: 'user:42',
),
));
// Filtering in the builder
SectionalBlocBuilder<MyCubit, MyState, MySection>(
sections: const [MySection.details],
objectId: 'user:42',
builder: (context, state) => ...,
);
If sections
is empty in an emission, it's treated as global (affects all builders). When objectId
is provided to a builder, it only reacts to emissions that match the same objectId
.
Tips #
- Use
sections: []
for global state transitions like initial loading. - Keep section enums small and meaningful (align to UI blocks).
- Prefer
objectId
overindex
when list order can change. - You can pass
listener:
toSectionalBlocBuilder
to get a section-awareBlocConsumer
.
License #
See LICENSE.