Flutter BLoC:如何在 Navigator.push 之后添加新事件而不用 BlocProvider 包装 MaterialApp
Flutter BLoC: How to add a new event after Navigator.push without wrapping MaterialApp with BlocProvider
所以我有启动我的 BLoC FilteredRecipesBloc 的屏幕 ExploreScreen
。
在此屏幕内,有一个导航到新屏幕的按钮 FilterScreen
。
从 FilterScreen
,我想添加一个影响两个屏幕的新事件。现在的问题是我收到此错误消息(onError Bad state: Cannot add new events after calling close)。如果不使用 BlocProvider 包装 MaterialApp 是否可行?我只想让本地集团访问两个屏幕。
探索屏幕:
class ExploreScreen extends StatefulWidget {
@override
_ExploreScreenState createState() => _ExploreScreenState();
}
class _ExploreScreenState extends State<ExploreScreen> {
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Explore"),
),
body: BlocProvider(
create: (context) => _filteredRecipesBloc,
child: BlocBuilder<FilteredRecipesBloc, FilteredRecipesState>(
builder: (context, state) {
if (state is FilteredRecipeEmpty) {
return CategoriesScreen();
}
if (state is FilteredRecipesLoading) {
return Column(
children: <Widget>[
CircularProgressIndicator(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_filteredRecipesBloc.add(UpdateRecipesFilter(
ingredients: ["Curry"], maxCookTime: 30));
},
),
],
);
}
if (state is FilteredRecipeLoaded) {
return ListView.builder(
itemCount: state.recipeList.length,
itemBuilder: (_, int index) {
return ImageRecipeContainer(recipe: state.recipeList[index]);
});
}
return Container();
}),
),
floatingActionButton: FloatingActionButton(
onPressed: _navigateToFilterScreen,
child: Icon(EvaIcons.funnelOutline),
heroTag: "fafdsf",
),
);
}
void _navigateToFilterScreen() {
Navigator.of(context)
.push(MaterialPageRoute<FilterScreen>(builder: (context) {
return BlocProvider.value(
value: _filteredRecipesBloc,
child: FilterScreen(_filteredRecipesBloc),
);
}));
}
@override
void dispose() {
_filteredRecipesBloc.close();
super.dispose();
}
}
过滤屏幕:
class FilterScreen extends StatefulWidget {
final FilteredRecipesBloc filteredRecipesBloc;
FilterScreen(this.filteredRecipesBloc);
@override
_FilterScreenState createState() => _FilterScreenState();
}
class _FilterScreenState extends State<FilterScreen> {
Map<String, bool> _selectedCategories = {};
Map<String, bool> _selectedIngredients = {};
@override
void initState() {
_initIngredientList();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Filter"),),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Category",style: TextStyle(fontSize: 20,fontWeight: FontWeight.bold),),
ChoiceChip(
label: Text("Vegan"),
selected: _selectedCategories["vegan"] == true,
onSelected: (isActive){
setState(() {
_selectedCategories["vegan"] = isActive;
});
},
),
Text("Ingredients"),
ShowMoreChoiceChips(children: _buildIngredientChoiceChips()),
RaisedButton(onPressed: _updateFilter),
],
),
),
),
);
}
void _initIngredientList(){
List<Recipe> recipeList =
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded).recipeList ?? [];
for(int i = 0; i < recipeList.length;i++){
for(int y = 0; y < recipeList[i].ingredientsFlat.length;y++){
_selectedIngredients[recipeList[i].ingredientsFlat[y].name] = false;
}
}
}
List<Widget> _buildIngredientChoiceChips(){
List<Widget> widgetList = [];
_selectedIngredients.forEach((key, value){
widgetList.add(ChoiceChip(label: Text(key), selected: value,onSelected: (isActive){
setState(() {
_selectedIngredients[key] = isActive;
});
},));
});
return widgetList;
}
void _updateFilter(){
List<String> ingredients = [];
_selectedIngredients.forEach((k,v){
if(v) ingredients.add(k);
});
widget.filteredRecipesBloc.add(
UpdateRecipesFilter(ingredients: ingredients.isNotEmpty ? ingredients : null));
//BlocProvider.of<FilteredRecipesBloc>(context).add(
// UpdateRecipesFilter(ingredients: ingredients.isNotEmpty ? ingredients : null),);
}
}
您不希望 StatefulWidget 控制您的 Bloc。您可以在 initState 方法中实例化您的 Bloc,但您不需要通过 dispose 方法关闭它,因为它会自动为您完成。
如果您已经在 initState 中创建了一个 Bloc 实例,您不想通过 BlocProvider 创建另一个实例。但是您应该使用命名构造函数 .value
.
两者都
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
BlocProvider.value(
value: _filteredRecipesBloc,
child: ...
)
或
// Preferable at least for me, because I don't need to bother with the instance of the Bloc.
BlocProvider(
create: (context) => FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList),
child: ...
)
class ExploreScreen extends StatefulWidget {
@override
_ExploreScreenState createState() => _ExploreScreenState();
}
class _ExploreScreenState extends State<ExploreScreen> {
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Explore"),
),
body: BlocProvider.value(
value: _filteredRecipesBloc,
child: BlocBuilder<FilteredRecipesBloc, FilteredRecipesState>(
builder: (context, state) {
if (state is FilteredRecipeEmpty) {
return CategoriesScreen();
}
if (state is FilteredRecipesLoading) {
return Column(
children: <Widget>[
CircularProgressIndicator(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_filteredRecipesBloc.add(UpdateRecipesFilter(
ingredients: ["Curry"], maxCookTime: 30));
},
),
],
);
}
if (state is FilteredRecipeLoaded) {
return ListView.builder(
itemCount: state.recipeList.length,
itemBuilder: (_, int index) {
return ImageRecipeContainer(recipe: state.recipeList[index]);
});
}
return Container();
}),
),
floatingActionButton: FloatingActionButton(
onPressed: _navigateToFilterScreen,
child: Icon(EvaIcons.funnelOutline),
heroTag: "fafdsf",
),
);
}
void _navigateToFilterScreen() {
Navigator.of(context)
.push(MaterialPageRoute<FilterScreen>(builder: (context) {
return BlocProvider.value(
value: _filteredRecipesBloc,
child: FilterScreen(_filteredRecipesBloc),
);
}));
}
}
在 flutter 主文档中查看此文档
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop.
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
}
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result.
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
所以我有启动我的 BLoC FilteredRecipesBloc 的屏幕 ExploreScreen
。
在此屏幕内,有一个导航到新屏幕的按钮 FilterScreen
。
从 FilterScreen
,我想添加一个影响两个屏幕的新事件。现在的问题是我收到此错误消息(onError Bad state: Cannot add new events after calling close)。如果不使用 BlocProvider 包装 MaterialApp 是否可行?我只想让本地集团访问两个屏幕。
探索屏幕:
class ExploreScreen extends StatefulWidget {
@override
_ExploreScreenState createState() => _ExploreScreenState();
}
class _ExploreScreenState extends State<ExploreScreen> {
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Explore"),
),
body: BlocProvider(
create: (context) => _filteredRecipesBloc,
child: BlocBuilder<FilteredRecipesBloc, FilteredRecipesState>(
builder: (context, state) {
if (state is FilteredRecipeEmpty) {
return CategoriesScreen();
}
if (state is FilteredRecipesLoading) {
return Column(
children: <Widget>[
CircularProgressIndicator(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_filteredRecipesBloc.add(UpdateRecipesFilter(
ingredients: ["Curry"], maxCookTime: 30));
},
),
],
);
}
if (state is FilteredRecipeLoaded) {
return ListView.builder(
itemCount: state.recipeList.length,
itemBuilder: (_, int index) {
return ImageRecipeContainer(recipe: state.recipeList[index]);
});
}
return Container();
}),
),
floatingActionButton: FloatingActionButton(
onPressed: _navigateToFilterScreen,
child: Icon(EvaIcons.funnelOutline),
heroTag: "fafdsf",
),
);
}
void _navigateToFilterScreen() {
Navigator.of(context)
.push(MaterialPageRoute<FilterScreen>(builder: (context) {
return BlocProvider.value(
value: _filteredRecipesBloc,
child: FilterScreen(_filteredRecipesBloc),
);
}));
}
@override
void dispose() {
_filteredRecipesBloc.close();
super.dispose();
}
}
过滤屏幕:
class FilterScreen extends StatefulWidget {
final FilteredRecipesBloc filteredRecipesBloc;
FilterScreen(this.filteredRecipesBloc);
@override
_FilterScreenState createState() => _FilterScreenState();
}
class _FilterScreenState extends State<FilterScreen> {
Map<String, bool> _selectedCategories = {};
Map<String, bool> _selectedIngredients = {};
@override
void initState() {
_initIngredientList();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Filter"),),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Category",style: TextStyle(fontSize: 20,fontWeight: FontWeight.bold),),
ChoiceChip(
label: Text("Vegan"),
selected: _selectedCategories["vegan"] == true,
onSelected: (isActive){
setState(() {
_selectedCategories["vegan"] = isActive;
});
},
),
Text("Ingredients"),
ShowMoreChoiceChips(children: _buildIngredientChoiceChips()),
RaisedButton(onPressed: _updateFilter),
],
),
),
),
);
}
void _initIngredientList(){
List<Recipe> recipeList =
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded).recipeList ?? [];
for(int i = 0; i < recipeList.length;i++){
for(int y = 0; y < recipeList[i].ingredientsFlat.length;y++){
_selectedIngredients[recipeList[i].ingredientsFlat[y].name] = false;
}
}
}
List<Widget> _buildIngredientChoiceChips(){
List<Widget> widgetList = [];
_selectedIngredients.forEach((key, value){
widgetList.add(ChoiceChip(label: Text(key), selected: value,onSelected: (isActive){
setState(() {
_selectedIngredients[key] = isActive;
});
},));
});
return widgetList;
}
void _updateFilter(){
List<String> ingredients = [];
_selectedIngredients.forEach((k,v){
if(v) ingredients.add(k);
});
widget.filteredRecipesBloc.add(
UpdateRecipesFilter(ingredients: ingredients.isNotEmpty ? ingredients : null));
//BlocProvider.of<FilteredRecipesBloc>(context).add(
// UpdateRecipesFilter(ingredients: ingredients.isNotEmpty ? ingredients : null),);
}
}
您不希望 StatefulWidget 控制您的 Bloc。您可以在 initState 方法中实例化您的 Bloc,但您不需要通过 dispose 方法关闭它,因为它会自动为您完成。
如果您已经在 initState 中创建了一个 Bloc 实例,您不想通过 BlocProvider 创建另一个实例。但是您应该使用命名构造函数 .value
.
两者都
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
BlocProvider.value(
value: _filteredRecipesBloc,
child: ...
)
或
// Preferable at least for me, because I don't need to bother with the instance of the Bloc.
BlocProvider(
create: (context) => FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList),
child: ...
)
class ExploreScreen extends StatefulWidget {
@override
_ExploreScreenState createState() => _ExploreScreenState();
}
class _ExploreScreenState extends State<ExploreScreen> {
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Explore"),
),
body: BlocProvider.value(
value: _filteredRecipesBloc,
child: BlocBuilder<FilteredRecipesBloc, FilteredRecipesState>(
builder: (context, state) {
if (state is FilteredRecipeEmpty) {
return CategoriesScreen();
}
if (state is FilteredRecipesLoading) {
return Column(
children: <Widget>[
CircularProgressIndicator(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_filteredRecipesBloc.add(UpdateRecipesFilter(
ingredients: ["Curry"], maxCookTime: 30));
},
),
],
);
}
if (state is FilteredRecipeLoaded) {
return ListView.builder(
itemCount: state.recipeList.length,
itemBuilder: (_, int index) {
return ImageRecipeContainer(recipe: state.recipeList[index]);
});
}
return Container();
}),
),
floatingActionButton: FloatingActionButton(
onPressed: _navigateToFilterScreen,
child: Icon(EvaIcons.funnelOutline),
heroTag: "fafdsf",
),
);
}
void _navigateToFilterScreen() {
Navigator.of(context)
.push(MaterialPageRoute<FilterScreen>(builder: (context) {
return BlocProvider.value(
value: _filteredRecipesBloc,
child: FilterScreen(_filteredRecipesBloc),
);
}));
}
}
在 flutter 主文档中查看此文档
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop.
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
}
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result.
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}