skip to Main Content

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"),
          ),
        ],
      ),
    ),
  ),
);
}
}

2

Answers


  1. Try to extend ChangeNotifier instead of using with modifier that is primarily used with mixins to add more feature on existing class which we do not required in this particular scenario.

    class AddonViewModel extends ChangeNotifier {
      /// CONSTRUCTOR
      AddonViewModel();
    
      @override
      void dispose() {
        super.dispose();
        try {
          paymentFinishedWebViewClosedSubscription?.cancel();
        } on Exception catch (_) {}
      }
    }
    

    this should work.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search