felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.8k stars 3.39k forks source link

BlocBuilder Widget List #379

Closed axellebot closed 5 years ago

axellebot commented 5 years ago

Is your feature request related to a problem? Please describe. I'm frustrated when building a list of widget depending on a BLoC state like in the example below (without condition ATM) :

Describe the solution you'd like Want to use the BlocBuilder to create a list of Widget and maybe also be able to use the spread ... operator.

...BlocBuilder<List<Widget>>(/*omitted*/)

First example :

  AppBar(
    actions: BlocBuilder<ProjectListEvent, ProjectListState>(
      bloc: _projectListBloc,
      builder: (BuildContext context, ProjectListState state) {
        widgetList = <Widget>[];

        // Conditional Action
        if (state is ProjectListLoaded) {
          Widget dropDownWidget = DropdownButton<ProjectSort>(
            items: [
              DropdownMenuItem<ProjectSort>(
                value: ProjectSort.name,
                child: Text('Name'),
              )
            ],
            onChanged: (value) {},
          );
          widgetList.add(dropDownWidget);
        }
        /*...*/

        // Non state conditional Actions
        Widget iconWidget = IconButton(
          icon: Icon(MdiIcons.delete),
          onPressed: () =>
              _projectListBloc.dispatch(ProjectListDefaultCleared()),
        );
        widgetList.add(iconWidget);
        /*...*/

        return widgetList;
      },
    ),
  );

Second example (with spread operator ...):

  AppBar(
    actions: <Widget>[
      // State conditional Actions
      ...BlocBuilder<ProjectListEvent, ProjectListState>(
        bloc: _projectListBloc,
        builder: (BuildContext context, ProjectListState state) {
          widgetList = <Widget>[];

          if (state is ProjectListLoaded) {
            Widget dropDownWidget = DropdownButton<ProjectSort>(
              items: [
                DropdownMenuItem<ProjectSort>(
                  value: ProjectSort.name,
                  child: Text('Name'),
                )
              ],
              onChanged: (value) {},
            );
            widgetList.add(dropDownWidget);
            /*...*/
          }
          return widgetList;
        },
      ),
      // Non state conditional Actions
      IconButton(
        icon: Icon(MdiIcons.delete),
        onPressed: () =>
            _projectListBloc.dispatch(ProjectListDefaultCleared()),
      ),
      /**/
    ],
  );

Describe alternatives you've considered I considered using the BlocBuilder to create the entire "action".

  AppBar(
    actions: <Widget>[
      BlocBuilder<ProjectListEvent, ProjectListState>(
        bloc: _projectListBloc,
        builder: (BuildContext context, ProjectListState state) {
          if (state is ProjectListLoaded) {
            return DropdownButton<ProjectListSort>(
              value: state.sort,
              hint: Text(AmasyLocalizations
                  .of(context)
                  .projectsSortHint),
              onChanged: (value) =>
                  _projectListBloc.dispatch(ProjectListQuery(sort: value)),
              items: [
                DropdownMenuItem<ProjectListSort>(
                  value: ProjectListSort.name,
                  child: Text("Name"),
                )
                /*...*/
              ],
            );
          }
          return Container();
        },
      ),
      BlocBuilder<ProjectListEvent, ProjectListState>(
        bloc: _projectListBloc,
        builder: (BuildContext context, ProjectListState state) {
          if (state is ProjectListLoaded) {
            return IconButton(
              icon: Icon(MdiIcons.delete),
              onPressed: () =>
                  _projectListBloc.dispatch(ProjectListDefaultSelected(projectId: null)),
            );
          }
          return Container();
        },
      ),
    ],
  );
felangel commented 5 years ago

Hi @axellebot 👋 Thanks for opening an issue!

Can you please provide a bit more information about how you want BlocBuilder to be used? I'm not sure I completely understand the problem you're running into.

Thanks! 👍

axellebot commented 5 years ago

Hi,

Want to use the BlocBuilder to create a list of Widget and maybe also be able to use the spread ... operator.

Let's forgot about the spread operator (...) for the moment.

For example if a widget property (like Row widget) need a widget list (aka children for Row widget), is there a way to make the BlocBuilder returning a list of widget instead of a widget ?

Since children are the only "conditionnal" content I don't want to wrap the Row widget into a BlocBuilder.

This way instead of having this :

  return BlocBuilder(
    bloc: bloc,
    builder: (BuildContext context, State state) {
      if (state is X) {
        return Row(
          children: <Widget>[
            Text('X'),
            Text('X'),
          ],
        );
      } else if (state is Y) {
        return Row(
          children: <Widget>[
            Text('Y'),
            Text('Y'),
          ],
        );
      }
    },
  );

I want to be able to do this :

  return Row(
    children: BlocBuilder(
      bloc: bloc,
      builder: (BuildContext context, State state) {
        if (state is X) {
          return <Widget>[
            Text('X'),
            Text('X'),
          ];
        } else if (state is Y) {
          return <Widget>[
            Text('Y'),
            Text('Y'),
          ];
        }
      },
    ),
  );

For this I was thinking about using Template and specifiying the type of return by the BlocBuilder. Extra : This way we can also use it for non-widget stuff (Don't fin any non "broken bloc pattern" example)

axellebot commented 5 years ago

Just made examples from the first comment more understandable. https://github.com/felangel/bloc/issues/379#issue-461032060

axellebot commented 5 years ago

I can't find any example where the Widget.build return a List.

Processing the conditional list of widget before using the list of widget seems to be possible with BlocListener use :


  List<Widget> conditionalActions = [];

  BlocListener<ProjectListEvent, ProjectListState>(
    bloc: _projectListBloc,
    listener: (BuildContext context, BlocAState state) {
      conditionalActions.clear();
      if (state is ProjectListLoaded) {
        Widget dropDownWidget = DropdownButton<ProjectSort>(
          items: [
            DropdownMenuItem<ProjectSort>(
              value: ProjectSort.name,
              child: Text('Name'),
            )
          ],
          onChanged: (value) {},
        );
        conditionalActions.add(dropDownWidget);
        /*...*/
      }
    },
    child: AppBar(
      actions: <Widget>[
        // State conditional Actions
        ...conditionalActions,
        // Non state conditional Actions
        IconButton(
          icon: Icon(MdiIcons.delete),
          onPressed: () =>
              _projectListBloc.dispatch(ProjectListDefaultCleared()),
        ),
        /**/
      ]
      ,
    ),
  );
felangel commented 5 years ago

@axellebot after thinking about this for a while I'm not sure it's feasible because flutter requires build to return a single widget. The approach you mentioned works and you can also use something like the Visibility widget in BlocBuilder to conditionally hide certain elements.

Thoughts?

axellebot commented 5 years ago

I'm not sure it's feasible because flutter requires build to return a single widget.

@felangel I also ended with the same though.

Well let's close this issue for the moment and wait a potential new flutter feature.

haashem commented 2 years ago

The right solution is move blocBuilder one Level up the tree, so wrap the entire appBar with blocBuilder then you have state and based on that you conditionally make your actions list.