I was trying to use provider for managing the state of TextField Controller and below is the code for the provider I'm using. I want to know why is dispose called! not being printed out to the console when I leave forgot password page. I tested dispose method of stateful widget and it works fine as expected.
// Forgot password page provider
import 'package:flutter/material.dart';
class ForgotPasswordPageProvider with ChangeNotifier {
final TextEditingController _textEditingController = TextEditingController();
final _formKey = GlobalKey<FormState>();
TextEditingController get textEditingController => _textEditingController;
GlobalKey<FormState> get formKey => _formKey;
@override
void dispose() {
_textEditingController.dispose();
print("dispose called!");
super.dispose();
}
}
// Forgot password page
import 'package:chat_app/providers/forgot_password_page_provider.dart';
import 'package:chat_app/utils/form_validator.dart';
import 'package:chat_app/widgets/custom_app_bar.dart';
import 'package:chat_app/widgets/form_input_field.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
class ForgotPasswordPage extends StatelessWidget {
const ForgotPasswordPage({super.key});
@override
Widget build(BuildContext context) {
final provider = Provider.of<ForgotPasswordPageProvider>(context);
return Scaffold(
appBar: CustomAppBar(
leading: IconButton(
onPressed: () {
GoRouter.of(context).pop();
},
icon: const FaIcon(FontAwesomeIcons.xmark),
),
title: "Reset password",
),
body: Padding(
padding: EdgeInsets.symmetric(
horizontal: 24,
vertical: MediaQuery.of(context).size.height * 0.06),
child: Form(
key: provider.formKey,
child: Column(
children: [
Hero(
tag: "logo",
child: FaIcon(
FontAwesomeIcons.comments,
color: Theme.of(context).colorScheme.primary,
size: 96,
),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
FormInputField(
controller: provider.textEditingController,
hintText: "Enter your email address",
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
validator: FormValidator.emailValidator,
label: "E-mail",
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
FilledButton(
onPressed: () {
if (provider.formKey.currentState!.validate()) {}
},
child: Text("Reset password"),
),
],
),
),
),
);
}
}
dispose
is not called because it is scoped to upper levels of your app. I can say that because you getting provider from context, meaning it is created somewhere in parent widgets, and not in-place.
To scope provider to current page (ForgotPasswordPage), you should create provider in build
method of that page. Something like:
@override
Widget build(BuildContext context) {
// Create Provider in place, and provide dispose method.
return ChangeNotifierProvider(
create: (context) => ForgotPasswordPageProvider(),
child: Scaffold(
appBar: CustomAppBar(
leading: IconButton(
onPressed: () {
GoRouter.of(context).pop();
},
icon: const FaIcon(FontAwesomeIcons.xmark),
),
title: "Reset password",
),
body: Padding(
padding: EdgeInsets.symmetric(
horizontal: 24,
vertical: MediaQuery.of(context).size.height * 0.06),
// Wrap form with Consumer builder to gather access to provider.
child: Consumer<ForgotPasswordPageProvider>(
builder: (context, provider, child) {
return Form(
key: provider.formKey,
child: Column(
children: [
Hero(
tag: "logo",
child: FaIcon(
FontAwesomeIcons.comments,
color: Theme.of(context).colorScheme.primary,
size: 96,
),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
FormInputField(
controller: provider.textEditingController,
hintText: "Enter your email address",
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
validator: FormValidator.emailValidator,
label: "E-mail",
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
FilledButton(
onPressed: () {
if (provider.formKey.currentState!.validate()) {}
},
child: Text("Reset password"),
),
],
),
);
}),
),
),
);
In that case, new instance of ForgotPasswordPageProvider
will be created each time when this page created. And dispose
will be called if this page is deleted from tree. Deleted is important, because dispose
should be called when Provider
is removed from tree, not just "invisible". That means, that if you use some routing, you should completely replace/delete route with that page to see dispose called!
, not just push some new page on top.
If you still want to create Provider
somewhere else in the app and call dispose
on current page, I do not think it is good idea. In that case, it will be hard to sync dispose
and recreation of provider and most probably you will get error, because you accessing your class after dispose
called.