I know there are several similar questions listed, but I can not resolve it after I tried some solutions from previous questions. :(
I want to do a network check and show a dialog if it has no network connection, so I did the following:
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
lazy: false,
create: (context) => NetworkBloc()
..add(
const StartMonitor(),
),
),
],
child: MaterialApp.router(
routerConfig: router,
builder: (context, child) {
return SubWidget(
child: child,
);
},
),
);
}
}
//try to create a sub-widget
class SubWidget extends StatelessWidget {
const SubWidget({super.key, this.child});
final Widget? child;
@override
Widget build(BuildContext context) {
return UpgradeAlert(
navigatorKey: router.routerDelegate.navigatorKey,
child: Builder(//try to add build
builder: (context) {
return BlocListener<NetworkBloc, NetworkState>(
listener: (context, state) async {
if (state is NetworkFailure) {
await showDialog(
context: context,
builder: (context) {
return const AlertDialog(
title: Text('No Network'),
);
});
}
},
child: child,
);
}),
);
}
}
the network connection check is fine, but the problem is with showing the dialog. I know there might be some problems with the context, so I tried to create a sub-widget or use a Builder (), but the problem persists.
The parameter builder
of MaterialApp
works as a wrapper above the inner widgets or inhereted widgets it creates (MediaQuery, Navigator, etc) so in that context those widgets don't exist yet (they are below). Your widget tree is something like this:
This is from the builder parameter
{@macro flutter.widgets.widgetsApp.builder}
Material specific features such as [showDialog] and [showMenu], and widgets
such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly function.
MaterialApp
(and WidgetsApp
if you don't want any material or related theme) is a widget that helps build other important aspects of your app so you don't waste time doing it over and over again for each project, that's why you pass it your themes, routes and everything and then with that information it create all the inner widgets required to make the app functional (Theme
, Tooltip
, Navigator
, ScaffoldMessenger
, MediaQuery
, etc).
After its done it nests all those widgets and pass the navigator from your router so all your code can use Theme.of
, Navigator.of
, and all those static methods requiring context (a locator of Flutter so it can find and instance of that object above it). Without MaterialApp
those methods will return null or an error (maybeOf and of respectively)
What the builder parameter does in MaterialApp is letting you build a widget between your other classes (Theme, MediaQuery) and the navigator, why? because some people would use that to put some global information and don't depend on the navigator and avoid being removed when using a pop. The builder gives you 2 values (BuildContext and Widget) and expects a widget. That context has all the information mentioned above except the Navigator, that one is the 2nd parameter (that widget is the navigator itself) so it expects you wrap in some other widget or whatever and return it somehow so you have your Widget with some tweaks (colors, dialogs logic, listeners) and then your navigator
What you need is to use your navigator key in your dialog context, and from there use navigatorKey.currentContext
;
class SubWidget extends StatelessWidget {
const SubWidget({super.key, this.child, required this navigator});
final Widget? child;
@override
Widget build(BuildContext context) {
return UpgradeAlert(
navigatorKey: router.routerDelegate.navigatorKey, /// the context of this key is the one required to make dialogs
child: Builder(//try to add build
builder: (context) {
return BlocListener<NetworkBloc, NetworkState>(
listener: (context, state) async {
if (state is NetworkFailure) {
await showDialog(
context: router.routerDelegate.navigatorKey.currentContext!, /// It may be null but if you initialize it in your route there shouldn't be a problem
builder: (context) {
return const AlertDialog(
title: Text('No Network'),
);
});
}
},
child: child,
);
}),
);
}
}
There is no need to make a Builder
wrapper. That's the reason UpdateDialog from that package asks you to pass the navigator key, to do exactly the same you're trying and avoid depending on other contexts