状态是否未传递给作为 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 小部件。
下面的代码应该
- 在启动时显示循环进度条 - 这工作正常
- 呼叫一堆 APIs - 这正在发生
- 同时在各种小部件中显示带有默认文本值的初始屏幕 - 会发生这种情况
- 由于 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 接收。
我是 Flutter Bloc 的新手,一定不知道 UI 小部件如何处理状态更改。在顶层,我有一个 BlocConsumer,在它下面,我嵌套了 BlocBuilder 小部件和 buildWhen 方法,以指示何时以及如何重建 Bloc 小部件。根据 print 语句,看起来 Bloc 状态在顶级 BlocConsumer 小部件中被消耗,并且永远不会下降到较低级别的 BlocBuilder 小部件。
下面的代码应该
- 在启动时显示循环进度条 - 这工作正常
- 呼叫一堆 APIs - 这正在发生
- 同时在各种小部件中显示带有默认文本值的初始屏幕 - 会发生这种情况
- 由于 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 接收。