Issue
So I have screen ExploreScreen
which initiates my BLoC FilteredRecipesBloc.
Inside this screen, there is a button that navigates to a new screen FilterScreen
.
From the FilterScreen
, I want to add a new event which affects both screens. The problem now is that I’m getting this error message (onError Bad state: Cannot add new events after calling close). Is this possible without wrapping MaterialApp with a BlocProvider? I just want local bloc access to two screens.
ExploreScreen:
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();
}
}
Filter Screen:
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),);
}
}
Solution
You don’t want StatefulWidget to controll your Bloc. You can instantiate your Bloc in initState method but you don’t need to close it via dispose method because it automatically does it for you.
If you have made an instance of the Bloc in initState, you don’t want to make another one via BlocProvider. But instead you should use the named constructor .value
.
Either
FilteredRecipesBloc _filteredRecipesBloc;
@override
void initState() {
_filteredRecipesBloc = FilteredRecipesBloc(
recipeList:
(BlocProvider.of<RecipesBloc>(context).state as RecipesLoaded)
.recipeList);
super.initState();
}
BlocProvider.value(
value: _filteredRecipesBloc,
child: ...
)
OR
// 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),
);
}));
}
}
Answered By – Federick Jonathan
Answer Checked By – Willingham (FlutterFixes Volunteer)