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
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.
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.
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 .
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),
);
}
}
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 ;)
StatefulWidget
, use:void bar(BuildContext context) async {
await yourFuture();
if (!mounted) return;
Navigator.pop(context);
}
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With