Hi,
I am trying to build my first app in Flutter using flutter_bloc. I tried many ways to solve this but never succeeded. I have a list of items that are fetched from the API using a stream and wanted if click on an item to view the details of that item. In the ItemDetailScreen:
`class ShowItemDetails extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return BlocListener
listener : (context, state) {
if (state is ItemDetailSuccessful ){
return Center(
child: Text("Item details"),
);
}else if (state is ItemDetailFailure ){
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children:
Text(state.message),
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Text('Retry'),
onPressed: () {
},
)
],
),
);
}else
return CircularProgressIndicator();
}
);
}`
somewhere in the listview widget i did this (follow link):
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ItemDetailsScreen(menuItemId: food.id))),
child: Container(
https://github.com/edgebasis/flutterApp/blob/master/lib/widgets/food_list.dart
error i get is:
`โโโโโโโโ Exception caught by widgets library โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The following assertion was thrown building ShowItemDetails:
BlocProvider.of() called with a context that does not contain a Bloc of type ItemDetailBloc.
No ancestor could be found starting from the context that was passed to BlocProvider.of<ItemDetailBloc>().`
I am not sure if i need to do anything else before i navigate to the detail screen.
thanks
Hi @edgebasis !
You're trying to use a BlocListener to render UI which will not work, since listeners are used for side effects like navigation, showing dialogs, etc. If you need to return pieces of your UI then please use BlocBuilder instead.
As for your BlocProvider error, make sure you have a bloc of the needed type above the widget where you're trying to access it, or else you'll get that error.
If you're still having issues please share a minimal repo/gist showcasing them.
Hi @RollyPeres ,
thanks for taking the time on Sunday!
I tried what you said but still i get the same error.
I am using a stream to fetch the data as you can see food_list.dart file. Not using the same bloc. does that affect?
here is the public gist:
https://gist.github.com/edgebasis/07a62df74a469ce850f320be6cbdbfa6
You're consuming ItemDetailBloc in your ItemDetailsScreen before you provide the bloc. Your bloc is only provided in ShowItemDetails widget. You should move your BlocProvider above your BlocBuilder.
I just moved the provider to ItemDetailsScreen widget and then moved the builder to ShowItemDetails widget.
Now we i navigate to the ItemDetailsScreen i get this error:
RepositoryProvider.of() called with a context that does not contain a repository of type MenuItemsRepository.
No ancestor could be found starting from the context that was passed to RepositoryProvider.of<MenuItemsRepository>().
is there anything else i need to do before navigating to the ItemDetailsScreen?
this is how the code looks like in the ItemDetailsScreen:
class ItemDetailsScreen extends StatelessWidget {
final int menuItemId;
ItemDetailsScreen({@required this.menuItemId});
@override
Widget build(BuildContext context) {
// final itemDetailBloc = BlocProvider.of<ItemDetailBloc>(context);
final itemDetailService = RepositoryProvider.of<MenuItemsRepository>(context);
return Scaffold(
appBar: AppBar(
title: Text(
"Item details",
style: TextStyle(color: Colors.black),
),
),
body: BlocProvider<ItemDetailBloc>(
create: (context) => ItemDetailBloc(itemDetailService),
child: SafeArea(
child: ShowItemDetails(),
),
));
}
}
class ShowItemDetails extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return BlocBuilder<ItemDetailBloc, ItemDetailState>(
builder: (context, state) {
if (state is ItemDetailSuccessful) {
return Container(
alignment: Alignment.center,
child: Container(
child: Text("Item details"),
),
);
} else if (state is ItemDetailFailure) {
return Container();
} else
return null;
});
}
}
thank you
@edgebasis you're getting that error because you're trying to obtain an instance of your repository using RepositoryProvider.of<MenuItemsRepository>(context) but you haven't actually provided that repository in your tree. You need to create it using RepositoryProvider(create: (_) => MenuItemsRepository()).
It's up to you if this is a globally available repository or you wanna scope it to your details page, in which case you should provide it just above BlocProvider<ItemDetailBloc> , which you can then initialize like ItemDetailBloc(context.repository<MenuItemsRepository>())
@RollyPeres thanks for the reply. I actually went and redone the bloc architecture from the list of items and wrapped it up with a provider. I think i did what you mentioned and that is to create the repository and that was in the root (list view of the items) see below:
Container(
height: 240,
width: 200,
child: BlocProvider(
create: (context) =>
MenuBloc(menuItemsRepository: MenuItemsRepository()),
child: FoodList(),
),
),
then in then in the FoodList widget i reconstructed everything as:
class _FoodListState extends State<FoodList> {
MenuBloc menuBloc;
@override
void initState() {
super.initState();
menuBloc = BlocProvider.of<MenuBloc>(context);
menuBloc.add(FetchMenuEvent());
}
@override
Widget build(BuildContext context) {
return Container(
child: Container(
child: BlocListener<MenuBloc, MenuState>(
listener: (context, state) {
if (state is MenuFailureState) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(state.message),
));
}
},
child: BlocBuilder<MenuBloc, MenuState>(builder: (context, state) {
if (state is MenuInitialState) {
return _buildLoadingWidget();
} else if (state is MenuLoadingState) {
return _buildLoadingWidget();
} else if (state is MenuLoadedState) {
return _buildMenuItemsListWidget(state.menu);
} else if (state is MenuFailureState)
return _buildErrorWidget(state.message);
else
return null;
}),
),
),
);
}
I then used the bellow method to navigate to the detail page and found that i dont really need use bloc in order to construct the details screen.
void _navigateToMenuItemDetailScreen(BuildContext context, Item food) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return ItemDetailsScreen(foodItem: food,);
}));
}
it works for now!
Glad you found a solution.
I'd also convert food list widget to a stateless one and add event when bloc is created.e.g.:
BlocProvider<MenuBloc>(create: (_) => MenuBloc()..add(FetchMenuEvent()))
Thanks @RollyPeres , i will definitely look in this solution too, it makes more sense to have a stateless widget for the Food list.
thank you
Most helpful comment
Thanks @RollyPeres , i will definitely look in this solution too, it makes more sense to have a stateless widget for the Food list.
thank you