bizz84 / bizz84/force_update_helper
Navigator Error when Using Sentry and ForceUpdateWidget
- Dominant language
- Dart
- Stars
- 50
- Forks
- 22
- PR merge metrics
- No merged PRs in 30d
Description
I am using Sentry and ForceUpdateWidget in my Flutter app. When I force the update dialog to appear, I encounter the following error:
`[sentry.platformError] [error] Uncaught Platform Error
[sentry.platformError] FlutterError (Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.)
[sentry.platformError] #0 Navigator.of. (package:flutter/src/widgets/navigator.dart:2886:9)
#1 Navigator.of (package:flutter/src/widgets/navigator.dart:2893:6)
navigator.dart:2893
#2 showDialog (package:flutter/src/material/dialog.dart:1457:19)
dialog.dart:1457
#3 InAppUpdate.showAlertDialog (package:sold_out_event/clients/in_app_update/in_app_update.dart:53:14)
in_app_update.dart:53
#4 InAppUpdate.build. (package:sold_out_event/clients/in_app_update/in_app_update.dart:30:61)
in_app_update.dart:30
#5 _ForceUpdateWidgetState._triggerForceUpdate (package:force_update_helper/src/force_update_widget.dart:82:54)
force_update_widget.dart:82
#6 _ForceUpdateWidgetState._checkIfAppUpdateIsNeeded (package:force_update_helper/src/force_update_widget.dart:66:22)
force_update_widget.dart:66
`
**Observations:**
This issue only occurs when wrapping runApp with SentryFlutter.init.
If I remove the Sentry wrapper, the ForceUpdateWidget dialog works as expected.
**Possible Cause:**
It seems that the context used to show the dialog is not properly associated with a Navigator when Sentry is initialized. This might be due to how the widget tree is structured when SentryFlutter.init wraps the app.
**Question:**
How can I ensure that showDialog is called with the correct BuildContext while keeping Sentry enabled?
This is my main.dart:
```dart
void main() async {
await Bootstrap.init();
await SentryFlutter.init(
(options) => options
..dsn = Environment.sentryDsn
..tracesSampleRate = 1.0
..profilesSampleRate = 1.0,
appRunner: () => runApp(const ProviderScope(child: App())),
);
}
```
And this is the widget that uses ForceUpdateWidget
```dart
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:force_update_helper/force_update_helper.dart';
import 'package:sold_out_event/config/environment/environment.dart';
import 'package:sold_out_event/config/extensions/l10n.dart';
import 'package:sold_out_event/config/utils/url_utils.dart';
class InAppUpdate extends StatelessWidget {
const InAppUpdate({
required this.navigatorKey,
required this.child,
this.allowCancel = false,
super.key,
});
final GlobalKey navigatorKey;
final Widget child;
final bool allowCancel;
@override
Widget build(BuildContext context) {
return ForceUpdateWidget(
onException: (error, stackTrace) => log('ERROR: $error, $stackTrace'),
navigatorKey: navigatorKey,
forceUpdateClient: ForceUpdateClient(
fetchRequiredVersion: () async => '2.2.0',
iosAppStoreId: Environment.appStoreId,
),
allowCancel: allowCancel,
showForceUpdateAlert: (context, allowCancel) async => showAlertDialog(
context: context,
title: context.loc.updateRequiredTitle,
content: context.loc.updateRequiredDescription,
defaultActionText: context.loc.updateNowCta,
),
child: child,
showStoreListing: (storeUrl) async => UrlUtils.launchAppUri(storeUrl),
);
}
Future showAlertDialog({
required BuildContext context,
required String title,
required String content,
required String defaultActionText,
String? cancelActionText,
bool isDestructive = false,
String? routeName,
}) {
if (kIsWeb ||
defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS) {
return showDialog(
context: context,
barrierDismissible: false,
routeSettings: RouteSettings(name: routeName),
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
if (cancelActionText != null)
TextButton(
child: Text(cancelActionText),
onPressed: () => Navigator.of(context).pop(false),
),
TextButton(
child: Text(defaultActionText),
onPressed: () => Navigator.of(context).pop(true),
),
],
),
);
}
return showCupertinoDialog(
context: context,
routeSettings: RouteSettings(name: routeName),
builder: (context) => CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: [
if (cancelActionText != null)
CupertinoDialogAction(
child: Text(cancelActionText),
onPressed: () => Navigator.of(context).pop(false),
),
CupertinoDialogAction(
isDestructiveAction: isDestructive,
onPressed: () => Navigator.of(context).pop(true),
child: Text(defaultActionText),
),
],
),
);
}
}
```
app.dart:
```dart
class App extends HookConsumerWidget {
const App({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appRouter = ref.read(appRouterProvider);
return MaterialApp.router(
routerConfig: appRouter.config(
navigatorObservers: () => [
PosthogObserver(),
],
),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
builder: (context, child) => MediaQuery.withNoTextScaling(
child: InAppUpdate(
navigatorKey: appRouter.navigatorKey,
child: child!,
),
),
...
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce the failure using SentryFlutter.init in main.dart and the MaterialApp.router setup in app.dart. Start at InAppUpdate.build and compare the context passed to showAlertDialog with the navigatorKey used by ForceUpdateWidget._triggerForceUpdate. Done means the force-update dialog opens successfully while Sentry remains enabled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, flutter
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100