Я пытался использовать поставщика для управления состоянием контроллера TextField, и ниже приведен код поставщика, который я использую. Я хочу знать, почему вызывается Dispose! не распечатывается на консоль, когда я покидаю страницу забытого пароля. Я протестировал метод удаления виджета с состоянием, и он работает нормально, как и ожидалось.
// 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
не вызывается, поскольку его область действия ограничена верхними уровнями вашего приложения. Я могу сказать это, потому что вы получаете провайдера из контекста, то есть он создается где-то в родительских виджетах, а не на месте.
Чтобы ограничить поставщика текущей страницей (ForgotPasswordPage), вам следует создать поставщика в методе build
этой страницы. Что-то вроде:
@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"),
),
],
),
);
}),
),
),
);
В этом случае новый экземпляр ForgotPasswordPageProvider
будет создаваться каждый раз при создании этой страницы. И dispose
будет вызван, если эта страница будет удалена из дерева. Удаление важно, потому что dispose
следует вызывать, когда Provider
удаляется из дерева, а не просто «невидимым». Это означает, что если вы используете какую-либо маршрутизацию, вам следует полностью заменить/удалить маршрут на этой странице, чтобы увидеть dispose called!
, а не просто нажать какую-то новую страницу сверху.
Если вы все еще хотите создать Provider
где-то еще в приложении и вызвать dispose
на текущей странице, я не думаю, что это хорошая идея. В этом случае будет сложно синхронизировать dispose
и воссоздать провайдера, и, скорее всего, вы получите сообщение об ошибке, поскольку вы получаете доступ к своему классу после вызова dispose
.
Попытка использовать универсальный тип Provider приведет к ошибке. В этом случае он должен иметь тип ChangeNotifierProvider. return ChangeNotifierProvider<ForgotPasswordPageProvider>( ...
@Абель, ты прав, спасибо! В этом случае даже dispose
будет вызываться автоматически (нет необходимости указывать Dispose: в качестве параметра), я соответствующим образом обновлю свой ответ.
Вам нужно вызвать провайдер.dispose() на вашей странице.