Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do not use BuildContexts across async gaps

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
      }
   }

UPDATE: 17/September/2022

It appears that BuildContext will soon have a "mounted" property

So you can do:

if (context.mounted)

It basically allows StatelessWidgets to check "mounted" too.

Reference: Remi Rousselet Tweet

like image 761
Bermjly Team Avatar asked Aug 21 '21 09:08

Bermjly Team


People also ask

Do not use Buildcontexts across async gaps provider?

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.

What is BuildContext flutter?

BuildContext is a locator that is used to track each widget in a tree and locate them and their position in the tree. The BuildContext of each widget is passed to their build method. Remember that the build method returns the widget tree a widget renders. Each BuildContext is unique to a widget.

Why do we use async in flutter?

Asynchronous function is a function that returns the type of Future. We put await in front of an asynchronous function to make the subsequence lines waiting for that future's result. We put async before the function body to mark that the function support await .


3 Answers

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),
    );
  }
}
like image 141
Guildem Avatar answered Oct 17 '22 07:10

Guildem


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 ;)

like image 29
mcgtrt Avatar answered Oct 17 '22 06:10

mcgtrt


In StatefulWidget, use:

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

In StatelessWidget or any other class, try this approach:

class Foo {
  void bar(BuildContext context, [bool mounted = true]) async {
    await yourFuture();
    if (!mounted) return;
    Navigator.pop(context);
  }
}
like image 16
iDecode Avatar answered Oct 17 '22 06:10

iDecode