状态是否未传递给作为 BlocConsumer 小部件的子级的 BlocBuilder 小部件?

Are states not passed to BlocBuilder widgets that are children of a BlocConsumer widget?

我是 Flutter Bloc 的新手,一定不知道 UI 小部件如何处理状态更改。在顶层,我有一个 BlocConsumer,在它下面,我嵌套了 BlocBuilder 小部件和 buildWhen 方法,以指示何时以及如何重建 Bloc 小部件。根据 print 语句,看起来 Bloc 状态在顶级 BlocConsumer 小部件中被消耗,并且永远不会下降到较低级别的 BlocBuilder 小部件。

下面的代码应该

  1. 在启动时显示循环进度条 - 这工作正常
  2. 呼叫一堆 APIs - 这正在发生
  3. 同时在各种小部件中显示带有默认文本值的初始屏幕 - 会发生这种情况
  4. 由于 API returns 和 Bloc 在流上传递状态,应重建适当的 UI 小部件,用流对象中的数据替换默认文本。 -- 这不会发生。

代码片段:

Bloc发布的RaspDataStates(仅供参考,未展示RaspDataState的所有子类):

@immutable
abstract class RaspDataState {}

class RaspInitialState extends RaspDataState {
  @override
  String toString() => "RaspInitialState";
}

class RaspForecastModels extends RaspDataState {
  final List<String> modelNames;
  final String selectedModelName;
  RaspForecastModels(this.modelNames, this.selectedModelName);
}
...

Bloc 只是为了展示如何初始化。代码似乎都可以正常工作,但未显示。

class RaspDataBloc extends Bloc<RaspDataEvent, RaspDataState> { 
  RaspDataBloc({required this.repository}) : super(RaspInitialState());

  @override
  RaspDataState get initialState => RaspInitialState();
  ...

现在转到 UI 小部件。

class SoaringForecast extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<RaspDataBloc>(
      create: (BuildContext context) =>
          RaspDataBloc(repository: RepositoryProvider.of<Repository>(context)),
      child: RaspScreen(repositoryContext: context),
    );
  }
} 


class RaspScreen extends StatefulWidget {
  final BuildContext repositoryContext;
  RaspScreen({Key? key, required this.repositoryContext}) : super(key: key);
  @override
  _RaspScreenState createState() => _RaspScreenState();
}

class _RaspScreenState extends State<RaspScreen>
    with SingleTickerProviderStateMixin, AfterLayoutMixin<RaspScreen> {
 // Executed only when class created
 @override
  void initState() {
    super.initState();
    _firstLayoutComplete = false;
    print('Calling series of APIs');
    BlocProvider.of<RaspDataBloc>(context).add(GetInitialRaspSelections());
    _mapController = MapController();
  }


 @override
  void afterFirstLayout(BuildContext context) {
    _firstLayoutComplete = true;
    print(
        "First layout complete. mapcontroller is set ${_mapController != null}");
    _setMapLatLngBounds();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        key: _scaffoldKey,
        drawer: AppDrawer.getDrawer(context),
        appBar: AppBar(
          title: Text('RASP'),
          actions: <Widget>[
            IconButton(icon: Icon(Icons.list), onPressed: null),
          ],
        ),
        body: BlocConsumer<RaspDataBloc, RaspDataState>(
            listener: (context, state) {
          print('In forecastLayout State: $state');  << Can see all streamed states here
          if (state is RaspDataLoadErrorState) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                backgroundColor: Colors.green,
                content: Text(state.error),
              ),
            );
          }
        }, builder: (context, state) {
          print('state is $state');   << Only see last streamed state here
          if (state is RaspInitialState || state is RaspDataLoadErrorState) {
            print('returning CircularProgressIndicator');
            return Center(
              child: CircularProgressIndicator(),
            );
          }
          print('creating main screen');   << Only see this when all streams complete
          return Padding(
              padding: EdgeInsets.all(8.0),
              child:
                  Column(mainAxisAlignment: MainAxisAlignment.start, children: [
                getForecastModelsAndDates(),
                getForecastTypes(),
                displayForecastTimes(),
                returnMap()
              ]));
        }));
  }

  Widget getForecastModelsAndDates() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.start,
      children: [
        Expanded(
          flex: 3,
          child: forecastModelDropDownList(), // ForecastModelsWidget()
        ),
        Expanded(
            flex: 7,
            child: Padding(
              padding: EdgeInsets.only(left: 16.0),
              child: forecastDatesDropDownList(),
            )),
      ],
    );
  }

// Display GFS, NAM, ....
  Widget forecastModelDropDownList() {
    return BlocBuilder<RaspDataBloc, RaspDataState>(
        buildWhen: (previous, current) {
      return current is RaspInitialState || current is RaspForecastModels;
    }, builder: (context, state) {
      if (state is RaspInitialState || !(state is RaspForecastModels)) {
        return Text("Getting Forecast Models");
      }
      var raspForecastModels = state;
      print('Creating dropdown for models');
      return DropdownButton<String>(
        value: (raspForecastModels.selectedModelName),
        isExpanded: true,
        iconSize: 24,
        elevation: 16,
        onChanged: (String? newValue) {
          BlocProvider.of<RaspDataBloc>(context)
              .add(SelectedRaspModel(newValue!));
        },
        items: raspForecastModels.modelNames
            .map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value.toUpperCase()),
          );
        }).toList(),
      );
    });
  }

 ... more BlocBuilder child widgets similar to the one above

控制台打印语句为:

Calling series of APIs
state is RaspInitialState
returning CircularProgressIndicator
First layout complete. mapcontroller is set true
... (First of bunch of API output displays - all successful)
state is RaspInitialState                  << Not sure why this occurs again
returning CircularProgressIndicator
... (More API output displays - all successful)
streamed RaspForecastModels
In forecastLayout State: Instance of 'RaspForecastModels' << Doesn't cause widget to be rebuild
streamed RaspForecastDates     << Other states being produced by Bloc 
In forecastLayout State: Instance of 'RaspForecastDates'
streamed RaspForecasts
In forecastLayout State: Instance of 'RaspForecasts'
In forecastLayout State: Instance of 'RaspForecastTime'
streamed RaspMapLatLngBounds
In forecastLayout State: Instance of 'RaspMapLatLngBounds'
state is Instance of 'RaspMapLatLngBounds'
creating main screen

任何关于我的错误的智慧之言将不胜感激。

我之前将其添加为评论,但后来发现 Whosebug 最初并未显示我的评论(我需要单击“显示更多”)。所以在这里它的可读性更好。

问题已解决。我需要移动线路:

BlocProvider.of<RaspDataBloc>(context).add(GetInitialRaspSelections()); 

从 initState() 方法到 afterFirstLayout()。

然后执行所有 blocbuilder 并正确构建 UI。为了回答我的标题问题,bloc 状态是广播的,可以被不同的 BlocBuilders 接收。