Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error message when Consumer.ref is read after the widget is unmounted #3521

Open
Justus-M opened this issue May 1, 2024 · 3 comments
Assignees
Labels
bug Something isn't working
Milestone

Comments

@Justus-M
Copy link

Justus-M commented May 1, 2024

Describe the bug
I seem to be getting the following error from ConsumerStatefulWidget (or rather, ConsumerState)

_TypeError
Null check operator used on a null value

framework.dart in State.context at line 958 within flutter
consumer.dart in ConsumerState.ref at line 500 within flutter_riverpod
In App
blaze_tooltip.dart in _BlazeTooltipWidgetState.initState. within query_composer
In App
binding.dart in SchedulerBinding._invokeFrameCallback at line 1386 within flutter
binding.dart in SchedulerBinding.handleDrawFrame at line 1322 within flutter
binding.dart in SchedulerBinding._handleDrawFrame at line 1169 within flutter
hooks.dart in _invoke at line 312
platform_dispatcher.dart in PlatformDispatcher._drawFrame at line 399
hooks.dart in _drawFrame at line 283

To Reproduce

Unfortunately I haven't been able to reproduce the error as I just see it in sentry, but here is the widget code:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:query_composer/main.dart';
import 'package:query_composer/core/providers/tooltips.dart';
import 'package:super_tooltip/super_tooltip.dart';
import 'package:visibility_detector/visibility_detector.dart';

const labels = {
  BlazeTooltip.resultOptions: 'Click here to Graph this, export data, and more',
  BlazeTooltip.queryOptions: 'Click here to save or edit the query',
  BlazeTooltip.supportChat: 'Click here for live customer support',
  BlazeTooltip.tableDescription: 'Click on a table to add more descriptions',
};

class BlazeTooltipWidget extends ConsumerStatefulWidget {
  final bool enabled;
  final Widget child;
  final BlazeTooltip tooltip;
  const BlazeTooltipWidget(
      {required this.child,
      required this.tooltip,
      this.enabled = true,
      Key? key})
      : super(key: key);

  @override
  ConsumerState<ConsumerStatefulWidget> createState() =>
      _BlazeTooltipWidgetState();
}

class _BlazeTooltipWidgetState extends ConsumerState<BlazeTooltipWidget> {
  var controller = SuperTooltipController();
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
      var wasNotAlreadyShown =
          !ref.read(tooltipsProvider).contains(widget.tooltip);
      var showTooltip = widget.enabled && wasNotAlreadyShown;
      if (showTooltip) {
        var waitDuration = widget.tooltip == BlazeTooltip.resultOptions ? 3 : 1;
        await Future.delayed(Duration(seconds: waitDuration));
        if (mounted && isVisible) controller.showTooltip();
      }
    });
  }

  var isVisible = true;
  var closedTooltip = false;

  @override
  Widget build(BuildContext context) {
    var direction = widget.tooltip == BlazeTooltip.tableDescription
        ? TooltipDirection.right
        : widget.tooltip == BlazeTooltip.supportChat
            ? TooltipDirection.up
            : TooltipDirection.down;

    var shouldShowTooltip = widget.enabled &&
        !ref.watch(tooltipsProvider).contains(widget.tooltip) &&
        isVisible &&
        !isTesting;
    if (!shouldShowTooltip) return widget.child;
    return VisibilityDetector(
        key: const Key('my-widget-key'),
        onVisibilityChanged: (visibilityInfo) {
          if (visibilityInfo.visibleFraction == 1) {
            isVisible = true;
            if (!closedTooltip) {
              controller.showTooltip();
            }
          } else {
            isVisible = false;
            controller.hideTooltip();
          }
        },
        child: SuperTooltip(
            showBarrier: false,
            barrierColor: Colors.black.withOpacity(0.4),
            popupDirection: direction,
            backgroundColor: Theme.of(context).textTheme.bodySmall!.color!,
            controller: controller,
            content: Padding(
              padding: const EdgeInsets.all(4.0),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(labels[widget.tooltip]!,
                      style: const TextStyle(
                          fontSize: 16,
                          color: Colors.black,
                          fontWeight: FontWeight.bold)),
                  InkWell(
                      onTap: () {
                        ref
                            .read(tooltipsProvider.notifier)
                            .seenToolTip(widget.tooltip);
                        controller.hideTooltip();
                      },
                      child: const Padding(
                        padding: EdgeInsets.only(left: 8.0),
                        child: Icon(
                          Icons.close,
                          color: Colors.black,
                          weight: 10,
                        ),
                      )),
                ],
              ),
            ),
            child: GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  controller.hideTooltip();
                  ref
                      .read(tooltipsProvider.notifier)
                      .seenToolTip(widget.tooltip);
                },
                child: widget.child)));
  }
}

Expected behavior
I would expect a more descriptive error or no error.

@Justus-M Justus-M added bug Something isn't working needs triage labels May 1, 2024
@rrousselGit
Copy link
Owner

Sounds like you used ref after the widget was unmounted.

@rrousselGit rrousselGit changed the title ConsumerState Null check operator used on a null value Improve error message when Consumer.ref is read after the widget is unmounted May 6, 2024
@rrousselGit rrousselGit added this to the Riverpod 3.0 milestone May 6, 2024
@Justus-M
Copy link
Author

Justus-M commented May 6, 2024

Sounds like you used ref after the widget was unmounted.

Thanks for the reply Remi. I think it would be helpful to get a stacktrace indicating where that's happening.

Do you have any idea where? Are there any bad practices in terms of using ref in this code? Perhaps in the async postframecallback? As mentioned, I haven't been able to reproduce this error (it was reported in sentry).

@rrousselGit
Copy link
Owner

Stacktrace wise, that's the first line of your stacktrace:

framework.dart in State.context at line 958 within flutter

You'd have the same issue if you were to use context directly, with the exact same error.

In debug mode, there's a better error message. But Flutter removes the error message in release mode. So you see a null exception instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants