AlertDialog without context in Flutter
Asked Answered
M

6

63

I want to show an AlertDialog when a http get fails. The function showDialog (https://api.flutter.dev/flutter/material/showDialog.html) has the parameter "@required BuildContext context", but I want to call the AlertDialog from my async function getNews(), which hasn't a context value.

By analogy with Java, where I use null for dialog without an owner, I tried to put context value to null, but it is not accepted.

This is my code:

  Future<dynamic> getNews() async {
    dynamic retVal;
    try {
      var response = await http.get(url));
      if (response.statusCode == HttpStatus.ok) {
        retVal = jsonDecode(response.body);
      }
    } catch (e) {
      alertDlg(?????????, 'Error', e.toString());
  }
    return
    retVal;
  }

  static Future<void> alertDlg(context, String titolo, String messaggio) async {
    return showDialog<void>(
        context: context,
        barrierDismissible: false, // user must tap button!
        builder: (BuildContext context) {
        return AlertDialog(
              title: Text(titolo),
        ...
    );
  }
Mazuma answered 23/5, 2019 at 18:5 Comment(3)
Pass your Build context from StatefulWidget where you'll display the http response to your getNews() function .Lumenhour
can you give an example please. I tried passing context to my new widget. It doesnt give any error but also doesnt show any alertShope
bypassing null not getting any compile time error but getting same Failed assertion: line 'context != null': is not true.Gingery
P
11

The dialog only needs the context to access it through an inherited navigateState. You can make your own modified dialog, or use my lib to do this.

https://pub.dev/packages/get

With it you can open dialog from anywhere in your code without context by doing this:

Get.dialog(SimpleDialog());
Pastiness answered 31/3, 2020 at 17:10 Comment(11)
That's not possible. You probably forgot to add "Get" to your MaterialApp. Change it to GetMaterialApp and everything will work.Pastiness
like this GetMaterialApp.dialog(SimpleDialog());Gingery
i tried this GetMaterialApp(title: UtilString.submit); getting compile time error.Gingery
No, on your main filePastiness
Controller controller = Get.find(); by this to show dialog ?Gingery
i trying to show success dialog in api success response .Gingery
dont force a 3rd party lib if it is not needed. see the answer with final navigatorKey = GlobalKey<NavigatorState>();Petulah
Accepted answer is wrong? That's a huge overstatement to say the least. How come out of the numerous ways to achieve this, only GetX/Get (your) way is the only correct one? I respect the hard work done on GetX but you can't shove it down people throats. I'm surprised this was marked as the accepted answer.Epicurean
The correct answer (now deleted) said "There is no way to do this in Flutter". I'm not pushing GetX anywhere, I just mentioned that it's possible to do this, just like GetX does. The most voted answer came long after my answer, just compare the dates.Pastiness
@jonatasBorges How will I close it without context ??Pohai
This answer states, "The accepted answer is wrong." However, this answer is the accepted answer. Is this like "this statement is false"? :)Omer
P
126

Solution without third-party libraries or storing BuildContext:

final navigatorKey = GlobalKey<NavigatorState>();

void main() => runApp(
  MaterialApp(
    home: HomePage(),
    navigatorKey: navigatorKey, // Setting a global key for navigator
  ),
);

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: SafeArea(
        child: Center(
          child: Text('test')
        )
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: showMyDialog, // Calling the function without providing it BuildContext
      ),
    );
  }
}

void showMyDialog() {
  showDialog(
    context: navigatorKey.currentContext,
    builder: (context) => Center(
      child: Material(
        color: Colors.transparent,
        child: Text('Hello'),
      ),
    )
  );
}

After setting a global key for navigator (navigatorKey parameter of MaterialApp class), its current state becomes accessible from any part of the code. We can pass its context to the showDialog function. Make sure not to use it before the first frame is built, otherwise the state will be null.

Basically, dialogs are just another type of routes like MaterialPageRoute or CupertinoPageRoute - they are all derived from the ModalRoute class and their instances are pushed into NavigatorState. Context is only needed to get the current navigator's state. A new route can be pushed without having a context if we have a global navigator key:

navigatorKey.currentState.push(route)

Unfortunately, _DialogRoute class (...\flutter\lib\src\widgets\routes.dart) used in showDialog function is private and unaccessible, but you make your own dialog route class and push it into the navigator's stack.

UPDATE: Navigator.of method has been updated and it's no longer needed to pass subtree context.

Pericarditis answered 13/5, 2020 at 11:45 Comment(14)
Great hack! The .overlay saved my day! (I found that if directly use navigatorKey.currentState.context, it will not work because the showDialog needs a context below the Navigator context.)Eufemiaeugen
How about using navigatorKey.currentContext? It seems to work, but I'm not sure if there are potential problems compared to currentState.overlay.context?Sarver
Unhandled Exception: NoSuchMethodError: The getter 'overlay' was called on null. E/flutter (30999): Receiver: null E/flutter (30999): Tried calling: overlayGingery
Unhandled Exception: NoSuchMethodError: The getter 'overlay' was called on null. getting errorGingery
@MagnusW You're right, navigator state's context can now be passed, because Navigator.of method got updated.Pericarditis
@Gingery Navigator's state is null. As I mentioned, do not access it before the first frame of the app is built (you're probably trying to do it in initState. You can use addPostFrameCallback).Pericarditis
@IgorKharakhordin no, I trying to show a dialog in the API success method in but getting error of context, I follow the void showMyDialog() method but still getting context error.Gingery
@Gingery have you set navigatorKey parameter of MaterialApp class?Pericarditis
@IgorKharakhordin How if the navigatorKey has been used by Catcher.navigatorKey?Impoverished
Nice solution saved me a lot of timeCostive
If you're sure your navigatorKey will already have a context by the time you want to show the dialog, force unwrap your currentContext variable like so context: navigatorKey.currentContext!Provencal
Is there something you can do to ensure that currentContext is not null early in the initialization of your app? I get nervous every time I put a ! in my code. 😅Hibernicism
I get error "No Material widget found. ListTile widgets require a Material widget ancestor." when I try to create a dialog with that currentContext.Webbed
Perfect great solution, much appreciated! Took us actually even hours to notice that showDialog() is actually widget-specific due to its requirement of the BuildContextFennell
P
11

The dialog only needs the context to access it through an inherited navigateState. You can make your own modified dialog, or use my lib to do this.

https://pub.dev/packages/get

With it you can open dialog from anywhere in your code without context by doing this:

Get.dialog(SimpleDialog());
Pastiness answered 31/3, 2020 at 17:10 Comment(11)
That's not possible. You probably forgot to add "Get" to your MaterialApp. Change it to GetMaterialApp and everything will work.Pastiness
like this GetMaterialApp.dialog(SimpleDialog());Gingery
i tried this GetMaterialApp(title: UtilString.submit); getting compile time error.Gingery
No, on your main filePastiness
Controller controller = Get.find(); by this to show dialog ?Gingery
i trying to show success dialog in api success response .Gingery
dont force a 3rd party lib if it is not needed. see the answer with final navigatorKey = GlobalKey<NavigatorState>();Petulah
Accepted answer is wrong? That's a huge overstatement to say the least. How come out of the numerous ways to achieve this, only GetX/Get (your) way is the only correct one? I respect the hard work done on GetX but you can't shove it down people throats. I'm surprised this was marked as the accepted answer.Epicurean
The correct answer (now deleted) said "There is no way to do this in Flutter". I'm not pushing GetX anywhere, I just mentioned that it's possible to do this, just like GetX does. The most voted answer came long after my answer, just compare the dates.Pastiness
@jonatasBorges How will I close it without context ??Pohai
This answer states, "The accepted answer is wrong." However, this answer is the accepted answer. Is this like "this statement is false"? :)Omer
S
1

Catch the exception where you make the getNews call if you use await, else use the catchError property of the Future.

Suter answered 23/5, 2019 at 18:31 Comment(0)
C
1

So you need a BuildContext to create a dialog but you don't have access to it. That's a common problem, you can refer to this StackOverflow question for one of the approaches to solve it (create a static dialog and show it from wherever you need).

Another approach you may consider is to pass the context when creating an async method or object as an argument. Make sure you null it when you're done.

Or you can make a flag (boolean) which becomes 'true' under a certain condition, and in one of the build() methods you always check that flag, and if it's 'true' - do your thing (show a dialog for instance).

Choroiditis answered 27/2, 2020 at 14:42 Comment(0)
H
-3
  1. Install GetX with: flutter pub add get
  2. create a dialog with Get.dialog
  3. add these line
Get.dialog(
      Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 40),
            child: Container(
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.all(
                  Radius.circular(20),
                ),
              ),
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Material(
                  child: Column(
                    children: [
                      const Text("Dialog with GetX without context")
                      Row(
                        children: [
                          const SizedBox(width: 10),
                          Expanded(
                            child: ElevatedButton(
                              style: ElevatedButton.styleFrom(
                                minimumSize: const Size(0, 45),
                                primary: Colors.green,
                                onPrimary: const Color(0xFFFFFFFF),
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(8),
                                ),
                              ),
                              onPressed: () {
                                Navigator.pop(Get.overlayContext!, true);
                              },
                              child: const Text(
                                'Cerrar',
                              ),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );

done!

Harkness answered 27/4, 2023 at 20:0 Comment(0)
F
-5

Easiest way to show alert anywhere: Use this package link and enter these lines wherever you want to show alert:

QuickAlert.show( context: context, type: QuickAlertType.error, text: 'Error Message');

Fibriform answered 28/12, 2022 at 12:51 Comment(3)
But your solution with QuickAlert needs a BuildContext context. The question was about "AlertDialog without context".Dispirited
I request you to please read the question carefully not just the heading. Secondly I didn't provide the context from the Build method of the class. I just provided the type of alert and the text.Fibriform
void showAlert(type, text) { QuickAlert.show( context: context, type: type, text: text, ); }Fibriform

© 2022 - 2024 — McMap. All rights reserved.