Home > Net >  Is it a bad practice if my bloc's state is a list of widgets?
Is it a bad practice if my bloc's state is a list of widgets?

Time:05-27

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. This list I use it in a popup menu. It 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 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:

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:

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 widget (you'll see places where I use ´state.last´):

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 more the implementation, however there shouldn't be anything wrong with them. The question is more about what is the correct approach to tackle this use case.

CodePudding user response:

It is a bad practice to have widgets in a bloc. Your bloc should not contain any widgets and imports from the Flutter framework. A bloc should only have Dart code and remain platform/environment independent.

This is the first rule of bloc architecture and the reason why engineers develop it at Google. They were trying to use the same business logic code for both Flutter and AngularDart apps and came up with BloC architecture. You can watch this video, :) how it all started: https://www.youtube.com/watch?v=kn0EOS-ZiIc

And check this link about bloc architecture: https://bloclibrary.dev/#/architecture

  • Related