felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

How to navigate to detail screen #1094

Closed mujtabamahmood closed 4 years ago

mujtabamahmood commented 4 years ago

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<ItemDetailBloc, ItemDetailState>( 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

narcodico commented 4 years ago

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.

mujtabamahmood commented 4 years ago

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

narcodico commented 4 years ago

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.

mujtabamahmood commented 4 years ago

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

narcodico commented 4 years ago

@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>())

mujtabamahmood commented 4 years ago

@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!

narcodico commented 4 years ago

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()))

mujtabamahmood commented 4 years ago

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