I'm using Provider package to expose a simple boolean variabile that allow to change the status of variable "inAsyncCall" of the ModalProgressHUD widget.
When i try to do somethings before navigate to another page, and i want to display the circlular progress indicator during that computation, when the Future terminated, the current widget has been disposed and i cannot use Navigator.push():
Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
this is my Provider with ChangeNotifier class:
class CartProvider with ChangeNotifier {
bool _inAsync = false;
bool get inAsync => _inAsync;
void setInAsync(bool flag) {
this._inAsync = flag;
notifyListeners();
}
}
I inject the provider before the MaterialApp widget like this:
void main() async {
runApp(App());
}
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => CartProvider(),
)
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
And this is the simple home page where i access via context the provider injected:
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
final cartProvider = context.watch<CartProvider>();
return ModalProgressHUD(
inAsyncCall: cartProvider.inAsync,
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('HOME'),
FirstStatefulWidget(),
],
),
),
bottomNavigationBar: SecondStatefulWidget(),
),
);
}
}
class FirstStatefulWidget extends StatefulWidget {
FirstStatefulWidget({Key? key}) : super(key: key);
@override
_FirstStatefulWidgetState createState() => _FirstStatefulWidgetState();
}
class _FirstStatefulWidgetState extends State<FirstStatefulWidget> {
late CartProvider cartProvider = context.read<CartProvider>();
Future doSomething() async {
cartProvider.setInAsync(true);
await Future.delayed(
Duration(seconds: 2),
() => {
cartProvider.setInAsync(false),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
},
);
}
@override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('do call'),
onPressed: doSomething,
),
);
}
}
class SecondStatefulWidget extends StatefulWidget {
SecondStatefulWidget({Key? key}) : super(key: key);
@override
_SecondStatefulWidgetState createState() => _SecondStatefulWidgetState();
}
class _SecondStatefulWidgetState extends State<SecondStatefulWidget> {
late CartProvider cartProvider = context.read<CartProvider>();
void goToAnotherPageAfterCall() async {
try {
cartProvider.setInAsync(true);
Future.delayed(
Duration(seconds: 2),
() => {
cartProvider.setInAsync(false),
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
},
);
} on Exception catch (e) {
cartProvider.setInAsync(false);
}
}
@override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('goToAnotherPage'),
onPressed: goToAnotherPageAfterCall,
),
);
}
}