How can I efficiently handle initialization and disposal of TextEditingController with Provider in Flutter?

43 Views Asked by At

I'm encountering issues with accessing deactivated widget ancestors when disposing of the controller.

  import 'package:exposed/constants/colors.dart';
import 'package:exposed/providers/commentprovider.dart';
import 'package:exposed/providers/userverifyprovider.dart';
import 'package:exposed/view/postScreen/comments/customtextcard.dart';
import 'package:exposed/helpers/styles.dart';
import 'package:exposed/services/firebaseServices/firebaseservice.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:provider/provider.dart';

import '../../../../components/mybutton.dart';
import '../../../../components/mytextfield.dart';

class CommentsStream extends StatefulWidget {
  final String postId;
  const CommentsStream({super.key, required this.postId});

  @override
  State<CommentsStream> createState() => _CommentsStreamState();
}

class _CommentsStreamState extends State<CommentsStream> {
  late FocusNode _focusNode;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _focusNode = FocusNode();
    Provider.of<CommentProvider>(context, listen: false).init();
  }

  @override
  void dispose() {
    _focusNode.dispose();
    Provider.of<CommentProvider>(context, listen: false).disposed();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final user = Provider.of<UserIdProvider>(context);
    return Scaffold(
      appBar: AppBar(
        title: Styles.heading(user.userId),
      ),
      backgroundColor: AppColors.bgColor,
      body: FocusScope(
        autofocus: true,
        child: StreamBuilder<QuerySnapshot>(
          /* a streams of unique post comments. which takes a string parameter. 
          Inside getcomments: 'where' clause is used.*/
          stream: FirestoreService().getcomments(widget.postId),
          builder:
              (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(
                child: CircularProgressIndicator(),
              );
            }
            if (snapshot.hasError) {
              return Center(
                child: Text('Error: ${snapshot.error}'),
              );
            }
            return snapshot.data!.docs.isNotEmpty
                ? ListView.builder(
                    shrinkWrap: true,
                    itemCount: snapshot.data!.docs.length,
                    itemBuilder: (context, index) {
                      /* A custom card for comments. has functionality to Focus
                      TextFeild*/
                      return Comments(
                          focusNode: _focusNode,
                          commentId: snapshot.data!.docs[index].id,
                          postId: widget.postId,
                          userName: snapshot.data!.docs[index].get('userId'),
                          text: snapshot.data!.docs[index].get('comment'),
                          avatarUrl:
                              'https://cdn.pixabay.com/photo/2015/10/07/12/17/post-976115_960_720.png');
                    },
                  )
                : Center(
                    child: Styles.whiteheading('Be the first to comment'),
                  );
          },
        ),
      ),
      bottomNavigationBar: SizedBox(
        height: Styles.getScreenheight(context) * .2,
        child: Column(
          children: [
            Consumer<CommentProvider>(
              builder: (context, value, child) {
                /*A custom text feild which takes multiple needed parameters.
                has a functionality to send comment and reply */
                return MyTextFeild(
                    focusNode: _focusNode,
                    prefixicon: value.txtbtnstring == 'reply' &&
                            value.commentcontroller.text.isNotEmpty
                        ? IconButton(
                            onPressed: () {
                              /* String value for a button inside text feild 
                              because we wanted to switch it with reply*/
                              value.settxtbtnstring('comment');
                            },
                            icon: const Icon(Icons.cancel_outlined))
                        : const Icon(Icons.comment),
                    controller: value.commentcontroller,
                    validator: 'validator',
                    suffixIcon: MyButton(
                        text: value.txtbtnstring == 'reply' &&
                                value.commentcontroller.text.isNotEmpty
                            ? 'reply'
                            : 'comment',
                        ontap: () {
                          /* postId can be accessed through 
                          snapshot.data.docs[index].id it is stored in commentidreturn
                          variable which is from our provider class name CommentProvider
                          */
                          value.txtbtnstring == 'reply' &&
                                  value.commentcontroller.text.isNotEmpty
                              ? FirestoreService().replyComment(
                                  widget.postId,
                                  value.personId,
                                  value.commentcontroller.text,
                                  value.commentidreturn)
                              : FirestoreService().addComment(widget.postId,
                                  value.personId, value.commentcontroller.text);
                          value.commentcontroller.clear();
                        }),
                    label: 'comment');
              },
            )
          ],
        ),
      ),
    );
  }
}

ProviderClass:

import 'package:flutter/material.dart';

class CommentProvider extends ChangeNotifier {
  String? _txtbtnstring;
  String? _commentidreturn;
  String? _personId;
  late TextEditingController _commentcontroller;

  String get txtbtnstring => _txtbtnstring ?? 'comment';
  String get commentidreturn => _commentidreturn ?? '';
  String get personId => _personId ?? '';
  TextEditingController get commentcontroller => _commentcontroller;

  void settxtbtnstring(String txtbtnstring) {
    _txtbtnstring = txtbtnstring;
    notifyListeners();
  }

  void setcommentidreturn(String commentidreturn) {
    _commentidreturn = commentidreturn;
    notifyListeners();
  }

  void setpersonid(String personId) {
    _personId = personId;
    notifyListeners();
  }

  void setcommentcontroller(TextEditingController commentcontroller) {
    _commentcontroller = commentcontroller;
    notifyListeners();
  }

  void init() {
    _commentcontroller = TextEditingController();
  }

  void disposed() {
    _commentcontroller.dispose();
  }

  void setcontrollertext(String controllertext) {
    _commentcontroller.text = controllertext;
  }
}

Using Provider: I attempted to use Provider to manage the state of the TextEditingController and dispose of it properly when needed. However, I encountered difficulties accessing the TextEditingController instance from another widget where disposal is required. I experimented with various combinations of initState(), dispose(), didChangeDependencies(), and other lifecycle methods to try to initialize and dispose of the TextEditingController at the appropriate times. However, I still encountered the error "Looking up a deactivated widget's ancestor is unsafe.

0

There are 0 best solutions below