309

I have noticed a new lint issue in my project.

Long story short:

I need to use BuildContext in my custom classes

flutter lint tool is not happy when this being used with aysnc method.

Example:

   MyCustomClass{

      final buildContext context;
      const MyCustomClass({required this.context});

      myAsyncMethod() async {
        await someFuture();
        # if (!mounted) return;          << has no effect even if i pass state to constructor
        Navigator.of(context).pop(); #   << example
      }
   }
7
  • doesn't seem wise to pass context to objects like that for the purpose of navigation. If your navigation stack changes after you have passed the context to MyCustomClass and you try to navigate again using the old context, you will get errors. Commented Aug 21, 2021 at 11:25
  • I agree, then how this scenario should be approached? Commented Aug 21, 2021 at 12:54
  • Use some state management like BloC, where you can trigger navigation when a state changes. So long as you do not store your context, but instead, use the context for navigation purposes without storing the instance. Commented Aug 21, 2021 at 12:59
  • 3
    Does this answer your question? stackoverflow.com/a/69512692/1032613 Commented May 27, 2022 at 4:28
  • There is no context.mounted property. Please update answer. Commented Dec 31, 2022 at 2:53

19 Answers 19

369

Update Flutter 3.7+ :

mounted property is now officially added to BuildContext, so you can check it from everywhere, whether it comes from a StatefulWidget State, or from a Stateless widget.

While storing context into external classes stays a bad practice, you can now check it safely after an async call like this :

class MyCustomClass {
  const MyCustomClass();

  Future<void> myAsyncMethod(BuildContext context) async {
    Navigator.of(context).push(/*waiting dialog */);
    await Future.delayed(const Duration(seconds: 2));
    if (context.mounted) Navigator.of(context).pop();
  }
}

// Into widget
  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: () => const MyCustomClass().myAsyncMethod(context),
      icon: const Icon(Icons.bug_report),
    );
  }
// Into widget

Original answer

Don't stock context directly into custom classes, and don't use context after async if you're not sure your widget is mounted.

Do something like this:

class MyCustomClass {
  const MyCustomClass();

  Future<void> myAsyncMethod(BuildContext context, VoidCallback onSuccess) async {
    await Future.delayed(const Duration(seconds: 2));
    onSuccess.call();
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: () => const MyCustomClass().myAsyncMethod(context, () {
        if (!mounted) return;
        Navigator.of(context).pop();
      }),
      icon: const Icon(Icons.bug_report),
    );
  }
}
Sign up to request clarification or add additional context in comments.

13 Comments

How can you do it in a StatelessWidget?
@stk I don't think you can, a StatelessWidget don't really have a lifecycle (no state). But if you want an async method, then you have a long-ish thing to do, and you want the app user to see something is happening, so you need a state. If you look at your app, you should see where you need to handle this state and create a Stateful widget.
@stk In stateless widget, you can use this: stackoverflow.com/a/69512692/11675817
@stk answer updated for stateless widgets on Flutter 3.7+
I get error the getter 'mounted' isn't defined for the type 'BuildContext' . I am using stateless widget
|
125

Use context.mounted*

In StatefulWidget/StatelessWidget or in any class that has BuildContext:

void foo(BuildContext context) async {
  await someFuture();
  if (!context.mounted) return;
  Navigator.pop(context); // No warnings now
}

* If you're in a StatefulWidget, you can also use just mounted instead of context.mounted

8 Comments

@BeniaminoBaggins StatelessWidget is always mounted, so you never have to pass mounted: false in them, but to get away with the warning, it's better to have a field named mounted set to true.
@BeniaminoBaggins This entire solution/concept is nowhere in the docs (as far as I know). Docs may suggest you to convert your StatelessWidget to StatefulWidget and then use mounted flag to check if the widget is in the tree.
Ah, I see mounted is on BuildContext now. And it is mentioned in the question.
the getter 'mounted' isn't defined for the type 'BuildContext' I get this error on my stateless widget
@Aseem Update your Flutter version using flutter upgrade.
|
44

If your class can extend from StatefulWidget then adding

if (!mounted) return;

would work!

EDIT

I had this issue again and again and here's the trick - use or declare variables using context before using async methods like so:

    MyCustomClass{
      const MyCustomClass({ required this.context });

      final buildContext context;
      
      myAsyncMethod() async {
        // Declare navigator instance (or other context using methods/classes)
        // before async method is called to use it later in code
        final navigator = Navigator.of(context);
       
        await someFuture();
        
        // Now use the navigator without the warning
        navigator.pop();
      }
    }

EDIT END

As per Guildem's answer, he still uses

if (!mounted) return;

so what's the point of adding more spaghetti code with callbacks? What if this async method will have to pass some data to the methods you're also passing context? Then my friend, you will have even more spaghetti on the table and another extra issue.

The core concept is to not use context after async bloc is triggered ;)

7 Comments

This solution does not work. It has exactly the same problem as if you had called Navigator.of(context).pop() directly from the async method. If this hides the related analyzer warning, it is only a bug in the analyzer.
what about if I don't have a stateful widget and I have that navigator code inside a normal class?
Hey guys, please see my answer after edit. I have found the solution for this problem after many tries myself as this issue was sometimes visiting me back and I said enough :)
As it is, your answer is wrong. Context can't be used without checking mounted before use on async method. On the custom class, without StatefulWidget implementation, you must let the caller of myAsyncMethod check a context it may not have itself here (because you gave context at the wrong step and it may have changed since constructor). There's no spaghetti code in the accepted answer. Only adaptation for the "Flutter isn't happy when I use context in my custom async method of my custom class". And there's no reason a service class do UI stuff itself. Not its role.
As a confirmation of my solution, you can even see the comments under your own answer, which states similarly, here you have also richer explanation why this solution is correct and is not used in any case only to silence the warning: stackoverflow.com/a/69512692/11675817. Your answer is not being denied, your answer is correct. Sometimes there are more complex functions which requires handling additional parameters on return and that makes it problematic - more code, and then resulting in spaghetti code. Using context before async gap is solution, also with connection with things like BLOC.
|
12

you can use this approach

myAsyncMethod() async {
  await someFuture().then((_){
     if (!mounted) return;          
        Navigator.pop(context); 
     }
});

Now in you can access mounted from context anywhere in the widget and in any class that is using BuildContext like this

if(context.mounted) {
// do your navigation 
} else {
   return;
}

UPDATE: 2025

In latest version of flutter it is recommended to use mounted directly inside StatefulWidget

instead of using

if (context.mounted) {
  pop(context);
}

Use

if (mounted) {
  pop(context);
}

you can check the details when to use context.mounted and mounted here: use_build_context_synchronously

Comments

10

If you want to use mounted check in a stateless widget its possible by making an extension on BuildContext

extension ContextExtensions on BuildContext {
  bool get mounted {
    try {
      widget;
      return true;
    } catch (e) {
      return false;
    }
  }
}

and then you can use it like this

if (context.mounted)

Inspiration taken from GitHub PR for this feature and it passes the same tests in the merged PR

2 Comments

I am looking for temporary solution for flutter version < 3.7. can i use this ?
Yes, i started using the solution with 3.3 i think, unsure if there are any issues with using it prior to that
10

This help for me.

/// transition to the main page after a pause of 3 seconds
Future<void> _navigateToMainScreen(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 3));
  if (context.mounted) {
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (context) => const MainScreen()));
  }
}

Comments

8

In Flutter 3.7.0 BuildContext has the property mounted. It can be used both in StatelessWidget and StatefulWidgets like this:

void bar(BuildContext context) async {
  await yourFuture();
  if (!context.mounted) return;
  Navigator.pop(context);
}

2 Comments

what if class is neither stateful not statless? e.g. any class with static methods and with no ui
@MuhammadUmairSaqib It is not about the widget. If you have a context, you can access the property mounted no matter if you are in a static class or not
6

The mounted property is now accessible from BuildContext, allowing you to verify its validity from any widget, whether it's a StatefulWidget or a StatelessWidget.

While it's generally advised against storing BuildContext in external classes, you can now confidently ensure its integrity after an asynchronous call:

class MyCustomClass {
  const MyCustomClass();

  Future<void> myAsyncMethod(BuildContext context) async {
    Navigator.of(context).push(/* waiting dialog */);
    await Future.delayed(const Duration(seconds: 2));
    if (context.mounted) {
      Navigator.of(context).pop();
    }
  }
}

// Within a widget
@override
Widget build(BuildContext context) {
  return IconButton(
    onPressed: () => const MyCustomClass().myAsyncMethod(context),
    icon: const Icon(Icons.bug_report),
  );
}

This approach guarantees safe usage of BuildContext in asynchronous operations, confirming its continued presence within the widget tree.

Comments

3

Easiest way I could found is

Future.delayed(Duration.zero, () {

    // your code which uses Buildcontext in async function

});

1 Comment

This solution (or variants of it) are already provided in existing, older answers. When answering older questions, please make sure you provide either a novel solution, or a significantly better explanation than existing answers. If an existing answer already has your preferred solution, consider upvoting that answer.
0

Bad:

if(!context.mounted) return;

Good:

if(!mounted) return;

if you check context.mounted and your context is already popped(if i press the back button) then you will get an exception and your app will be crashed.

always use mounted if you want to check your current context is not popped.

safe and secure.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
-1

To avoid this in StatelessWidget you can refer to this example

class ButtonWidget extends StatelessWidget {
  final String title;
  final Future<String>? onPressed;

  final bool mounted;

  const ButtonWidget({
    super.key,
    required this.title,
    required this.mounted,
    this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const SizedBox(height: 20),
        Expanded(
            child: ElevatedButton(
          onPressed: () async {
            final errorMessage = await onPressed;
            if (errorMessage != null) {
              // This to fix: 'Do not use BuildContexts across async gaps'
              if (!mounted) return;
              snackBar(context, errorMessage);
            }
          },
          child: Text(title),
        ))
      ],
    );
  }
}

Comments

-1

For anyone who is having this problem nowadays, a simple solution is to use a conditional with context.mount Example:

 InkWell(
   onTap: () async {
             
     if (await controller.getOrderDetails(context,
          controller.orders.value[index].id!, index) 
          && >>> context.mounted <<<
            ) {
            await showModalBottomSheet(
             shape: const RoundedRectangleBorder(
             borderRadius: BorderRadius.only(
               topLeft: Radius.circular(6.0), topRight:Radius.circular(6.0))),
             context: context,
             builder: (context) => DraggableScrollableSheet(
               expand: false,
               minChildSize: .4,
               initialChildSize: .4,
               maxChildSize: .85,
               builder: (context, scrollController) => Column(
                 mainAxisAlignment: MainAxisAlignment.start,
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: [const DraggableIndicator(), widget],
              ),
            ),
          }
        }...
    ...

You will see that the warning in context will disappear

1 Comment

this way the context will stop showing the warning or misuse
-1
void onButtonTapped() async {
//Capture the context before the async operation
final navigator = Navigator.of(context);
await Future.delayed(const Duration(seconds: 1));

// Use the captured navigator context after the async operation
navigator.push(MaterialPageRoute(builder: (context) => const             ScreenChooseAmount()) );
}

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
-1

i think your problem happens when you try to navigate using Navigatorlike you showed : Navigator.of(context).pop(); "context" here is not recommended as it's taken from where the async happened before!

the solution in your case is to use GetIt and GoRoute together to initialise an instance of a router for the whole app like so :

instanceGetIt.registerLazySingleton<RoutesManager>(
  () => RoutesManager(isOnBoarding: true, isLoggedIn: isLoggedIn));

and then start to consume it within the other screens without contexts like so :

 instanceGetIt<RoutesManager>()
                                .router
                                .push('/your_desired_route');

Comments

-2

using buildcontext across async method or class is now not work in current version of flutter rather than introduce context in async await use method callback for navigation and other purposes

Comments

-3

Just simplify create a function to call the navigation

void onButtonTapped(BuildContext context) {
  Navigator.of(context).pop();
}

1 Comment

Does not answer the question, which is about context in async calls. (also, formatting issues)
-4

just save your navigator or whatever needs a context to a variable at the beginning of the function

      myAsyncMethod() async {
        final navigator = Navigator.of(context); // 1
        await someFuture();
        navigator.pop();  // 2
      }

4 Comments

No be careful of doing that! context might not be mounted so absolutely this does not guarantee to fix the issue and might cause the app to crash instead!
@BermjlyTeam I do not understand how this might not fix the issue. The BuildContext was used to fetch the Navigator instance. The resulting instance is now fetched. After the await, we must not use the BuildContext. We don't. We only use the Navigator instance. How does this not fix the issue in at least a StatelessWidget ?
@Sxndrome imagine that your someFuture() waits 5 seconds (biiiiig task). During this time, you go back with Android back button or another implemented way. What //2 will do when someFuture() is finished ? Context before and after the future won't always be the same.
This is very close to being the correct answer. It just needs a check before calling navigator.pop(). Change to if (navigator.mounted) navigator.pop(); I would also store the NavigatorState instead of BuildContext: MyCustomClass { final NavigatorState navigator; const MyCustomClass(this.navigator); } Example instantiation: final obj = MyCustomClass(Navigator.of(context));
-4

Add use_build_context_synchronously rule in your analysis_options.yaml file:

linter:
  rules:
    use_build_context_synchronously: false

This rule will remove all "Do not use BuildContexts across async gaps" warning in your code.

1 Comment

You may turn off the warning but doing so will increase the risk of encountering bugs
-11

DO NOT use BuildContext across asynchronous gaps.

Storing BuildContext for later usage can easily lead to difficult to diagnose crashes. Asynchronous gaps are implicitly storing BuildContext and are some of the easiest to overlook when writing code.

When a BuildContext is used from a StatefulWidget, the mounted property must be checked after an asynchronous gap.

So, I think, you can use like this:

GOOD:

class _MyWidgetState extends State<MyWidget> {
  ...

  void onButtonTapped() async {
    await Future.delayed(const Duration(seconds: 1));

    if (!mounted) return;
    Navigator.of(context).pop();
  }
}

BAD:

void onButtonTapped(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  Navigator.of(context).pop();
}

1 Comment

You can't just copy and paste directly from documentation and not at least cite your source: dart-lang.github.io/linter/lints/…

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.