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.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sectional_bloc_builder/sectional_bloc_builder.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sectional Bloc Builder Example',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
home: BlocProvider(
create: (_) => ProfileCubit(),
child: const ProfilePage(),
),
);
}
}
// 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: 800));
emit(state.copyWith(
email: newEmail,
uiStatus: UiSectionStatus(
sections: const [ProfileSection.details],
status: RequestStatus.success,
),
));
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: const Padding(
padding: EdgeInsets.all(16),
child: ProfileView(),
),
);
}
}
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}',
style: Theme.of(context).textTheme.headlineSmall);
},
),
const SizedBox(height: 16),
// 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: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
],
);
},
),
const SizedBox(height: 24),
// Rebuilds when submitButton is targeted (here we don't drive loading
// for it, but this shows the pattern)
SectionalBlocBuilder<ProfileCubit, ProfileState, ProfileSection>(
sections: const [ProfileSection.submitButton],
builder: (context, state) {
final busy = state.uiStatus.isLoading &&
state.uiStatus.containsSection(ProfileSection.submitButton);
return ElevatedButton.icon(
onPressed: busy
? null
: () =>
context.read<ProfileCubit>().updateEmail('new@example.com'),
icon: const Icon(Icons.save),
label: Text(busy ? 'Saving…' : 'Save'),
);
},
),
const SizedBox(height: 12),
Text(
'Tap Save to update only the details row.\n'
'Notice header and button don\'t rebuild.',
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
}