I am using flutter_bloc package. Is it a bad practice if I have a bloc whose state is a list of widgets? I use this bloc to push (add) and pop (remove) widgets/mini-screens from a list. I use this list the body of a popup menu, which has something like an embedded navigation. The last member of the list is the widget that is displayed in the popover.
Every time I push or pop, I emit a new state. The bloc is useful since that way I can call push or pop from anywhere in the widgets/mini-screens I display in my popover. Please let me know if my use-case is clear or if you need further details. Thanks.
Here are the relevant pieces of code:
Custom stack (where E
will be of type Widget
):
class PopoverStack<E> {
PopoverStack() : _storage = <E>[];
final List<E> _storage;
void push(E element) {
_storage.add(element);
}
void pop() {
_storage.removeLast();
}
E get last => _storage.last;
bool get isEmpty => _storage.isEmpty;
bool get isNotEmpty => !isEmpty;
PopoverStack.of(PopoverStack<E> stack) : _storage = List<E>.of(stack._storage);
}
Bloc for stack (PopoverPage
is an abstract class widgets will extend):
class PopoverCardStackBloc extends Cubit<PopoverStack<PopoverPage>> {
PopoverCardStackBloc(PopoverStack<PopoverPage> popoverStack) : super(popoverStack);
void push(PopoverPage element) {
emit(PopoverStack.of(state..push(element)));
}
void pop() {
emit(PopoverStack.of(state..pop()));
}
}
Popover body (here you'll see places where I use state.last
as Widget
):
class PopoverCardBody extends StatelessWidget {
const PopoverCardBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<PopoverCardStackBloc, PopoverStack<PopoverPage>>(
builder: (context, state) {
state;
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(16),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AnimatedContainer(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)),
height: state.last.height,
width: 429,
duration: const Duration(milliseconds: 200),
curve: Curves.decelerate,
child: Column(
children: [
Container(
height: 72,
padding: const EdgeInsets.all(16),
color: AppColors.backgroundLight.withOpacity(.5),
child: CenteredTitleBar(
title: state.last.title,
leadingChild: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: state.last.showBackButton
? () {
context.read<PopoverCardStackBloc>().pop();
}
: () {
BookingCard.tooltip.close();
},
child: state.last.showBackButton
? const Icon(
Icons.chevron_left,
color: Colors.white,
size: 24,
)
: const Text(
'Close',
style: TextStyle(
color: AppColors.textWhite,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
trailingChild: _buildActionButton(context),
),
),
Expanded(
flex: 80,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: state.last as Widget,
),
)
],
),
),
),
);
},
);
}
Widget _buildActionButton(BuildContext context) {
switch (context.read<PopoverCardStackBloc>().state.last.editButtonType) {
case StackActionButtonType.NONE:
return const SizedBox.shrink();
case StackActionButtonType.EDIT:
return MockActionButton(
labelPadding: const EdgeInsets.only(right: 16, left: 16, top: 7, bottom: 9),
backgroundColor: AppColors.accentButton,
borderRadius: BorderRadius.circular(8),
splashColor: AppColors.transparent,
label: 'Edit',
textStyle: const TextStyle(
color: AppColors.textWhite,
fontSize: 16,
fontWeight: FontWeight.w600,
),
onTap: () {
context.read<PopoverCardStackBloc>().push(const EditReservationPage());
},
);
case StackActionButtonType.SAVE:
return MockActionButton(
labelPadding: const EdgeInsets.only(right: 16, left: 16, top: 7, bottom: 9),
backgroundColor: AppColors.accentButton,
borderRadius: BorderRadius.circular(8),
splashColor: AppColors.transparent,
label: 'Save',
textStyle: const TextStyle(
color: AppColors.textWhite,
fontSize: 16,
fontWeight: FontWeight.w600,
),
onTap: () {
//TODO: make request with PopoverEditCardStackBloc state to update booking when api is ready.
BookingCard.tooltip.close();
},
);
}
}
}
These classes are just here for the one who wants to understand the approach more, however there shouldn't be anything wrong with them. The question is more about what is the correct way to tackle the use case described.