Home > Software engineering >  Bloc provider above OverlayEntry flutter
Bloc provider above OverlayEntry flutter

Time:12-31

I am having some problems with my flutter app. I am trying to add an overlay like this in the photo below:

enter image description here

And it works just fine, I am able to open it on long press and close it on tap everywhere else on the screen. The problem is that those two buttons - delete and edit - should call a bloc method that then do all the logic, but I do not have a bloc provider above the OverlayEntry. This is the error:

Error: Could not find the correct Provider<BrowseBloc> above this _OverlayEntryWidget Widget

This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:

- You added a new provider in your `main.dart` and performed a hot-reload.
  To fix, perform a hot-restart.

- The provider you are trying to read is in a different route.

  Providers are "scoped". So if you insert of provider inside a route, then
  other routes will not be able to access that provider.

- You used a `BuildContext` that is an ancestor of the provider you are trying to read.

  Make sure that _OverlayEntryWidget is under your MultiProvider/Provider<BrowseBloc>.
  This usually happens when you are creating a provider and trying to read it immediately.

  For example, instead of:

  ```
  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // Will throw a ProviderNotFoundError, because `context` is associated
      // to the widget that is the parent of `Provider<Example>`
      child: Text(context.watch<Example>().toString()),
    );
  }
  ```

  consider using `builder` like so:

  ```
  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // we use `builder` to obtain a new `BuildContext` that has access to the provider
      builder: (context, child) {
        // No longer throws
        return Text(context.watch<Example>().toString());
      }
    );
  }
  ```

If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter

I've already encountered this error but this time I'm in a bit of trouble because I'm working with an overlay and not a widget.

This is my code:

late OverlayEntry _popupDialog;

class ExpenseCard extends StatelessWidget {
  const ExpenseCard({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<AppBloc, AppState>(
      listener: (context, state) {},
      buildWhen: (previous, current) => previous.theme != current.theme,
      builder: (context, state) {
        return Column(
          children: [
            GestureDetector(
              onLongPress: () {
                _popupDialog = _createOverlay(expense);
                Overlay.of(context)?.insert(_popupDialog);
              },
              child: Card(
                ...some widgets
              ),
            ),
            const Divider(height: 0),
          ],
        );
      },
    );
  }
}

OverlayEntry _createOverlay(Expenses e) {
  return OverlayEntry(
    builder: (context) => GestureDetector(
      onTap: () => _popupDialog.remove(),
      child: AnimatedDialog(
        child: _createPopupContent(context, e),
      ),
    ),
  );
}

Widget _createPopupContent(BuildContext context, Expenses e) {
  return GestureDetector(
    onTap: () {},
    child: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Container(
          width: MediaQuery.of(context).size.width * 0.9,
          decoration: BoxDecoration(
            color: LocalCache.getActiveTheme() == ThemeMode.dark ? darkColorScheme.surface : lightColorScheme.surface,
            borderRadius: const BorderRadius.all(Radius.circular(16)),
          ),
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              ...some other widgets
            ],
          ),
        ),
        SizedBox(
          width: 256,
          child: Card(
            child: Column(
              children: [
                InkWell(
                  onTap: () {
                    _popupDialog.remove();
                    // This is where the error is been thrown
                    context.read<BrowseBloc>().add(SetTransactionToEdit(e));
                    showBottomModalSheet(
                      context,
                      dateExpense: e.dateExpense,
                      total: e.total,
                      transactionToEdit: e,
                    );
                  },
                  child: Padding(
                    padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
                    child: Row(
                      children: [Text(AppLocalizations.of(context).edit), const Spacer(), const Icon(Icons.edit)],
                    ),
                  ),
                ),
                const Divider(height: 0),
                InkWell(
                  onTap: () {
                    _popupDialog.remove();
                    // This is where the error is been thrown
                    context.read<BrowseBloc>().add(DeleteExpense(e.id!, e.isExpense));
                  },
                  child: Padding(
                    padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
                    child: Row(
                      children: [Text(AppLocalizations.of(context).delete), const Spacer(), const Icon(Unicons.delete)],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    ),
  );
}

How can I add the bloc provider above my OverlayEntry? Is this the best course of action?

Thank you to everyone that can help!

CodePudding user response:

Wrap your widget that you use in OverlayEntry in BlocProvider.value constructor and pass the needed bloc as an argument to it, like so

OverlayEntry _createOverlay(Expenses e, ExampleBloc exampleBloc) {
  return OverlayEntry(
    builder: (context) => GestureDetector(
      onTap: () => _popupDialog.remove(),
      child: BlocProvider<ExampleBloc>.value(
        value: exampleBloc,
        child: AnimatedDialog(
          child: _createPopupContent(context, e),
        ),
      ),
    ),
  );
}

CodePudding user response:

I have found a solution starting from the answer of Olga P, but changing one thing. I use the BlocProvider.value but I am passing as an argument to the method the context and not the bloc itself. This is the code:

OverlayEntry _createOverlay(Expenses e, BuildContext context) {
  return OverlayEntry(
    builder: (_) => GestureDetector(
      onTap: () => _popupDialog.remove(),
      child: BlocProvider<BrowseBloc>.value(
        value: BlocProvider.of(context),
        child: AnimatedDialog(
          child: _createPopupContent(context, e),
        ),
      ),
    ),
  );
}

With this change the two methods - edit and delete - work perfectly. Thanks to everyone who replied, I learned something today too!

CodePudding user response:

The problem is that you are using a function and not a widget. So you can either modify _createOverlay to be stateless or stateful widget, or you can pass the bloc as an argument to the function.

In the latter case this would be _createOverlay(expense, context.read<AppBloc>())

  • Related