markdown-editable-textinput icon indicating copy to clipboard operation
markdown-editable-textinput copied to clipboard

The outside controller may not be disposed.

Open pyjserv opened this issue 3 years ago • 2 comments

The outside TextEditingController should not be disposed of in the inner of _MarkdownTextInputState, because the outside controller may have its life cycle.

In my case, I use markdown-editable-text input and flutter_markdown in one widget by switching on focus or not and sharing TextEditingController between these. When the widget dismisses focus, _MarkdownTextInputState will dispose TextEditingController. That will cause an exception.

Here is my widget:

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:plans/markdown_editable_textinput/format_markdown.dart';
import 'package:plans/markdown_editable_textinput/markdown_text_input.dart';
import 'package:url_launcher/url_launcher.dart';

class MarkdownField extends ConsumerStatefulWidget {
  final String? initialValue;
  final ValueChanged<String>? onChanged;
  final bool? enable;
  final TextStyle? style;
  final String? hintText;

  const MarkdownField({
    Key? super.key,
    this.initialValue,
    this.onChanged,
    this.enable,
    this.style,
    this.hintText,
  });

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => MarkdownFieldState();
}

class MarkdownFieldState extends ConsumerState<MarkdownField> {
  bool focus = false;
  bool enable = true;
  var controller = TextEditingController();
  var focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    focusNode.addListener(() => onFocusChange());
    controller.text = widget.initialValue ?? "";
    enable = widget.enable ?? true;
  }

  void onFocusChange() {
    if (this.mounted) {
      setState(() {
        focus = focusNode.hasFocus;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return focus && enable ? inputTextField() : MarkdownView();
  }

  void onTextChanged(String value) {
    // to walkaround "setState() or markNeedsBuild called during build" error
    // https://stackoverflow.com/questions/47592301/setstate-or-markneedsbuild-called-during-build
    Future.delayed(Duration.zero, () async {
      widget.onChanged?.call(value);
    });
  }

  Widget inputTextField() => Focus(
        focusNode: focusNode,
        child: MarkdownTextInput(
          onTextChanged,
          controller.text,
          // FIXME: MarkdownTextInput will dispose controller.
          controller: controller,
          maxLines: null,
          actions: MarkdownType.values,
          label: widget.hintText,
        ),
      );

  Widget MarkdownView() => GestureDetector(
        behavior: HitTestBehavior.translucent,
        onTap: () {
          focusNode.requestFocus();
        },
        child: Focus(
          focusNode: focusNode,
          child: Container(
            constraints: BoxConstraints(
              minHeight: 50,
            ),
            alignment: Alignment.centerLeft,
            decoration: BoxDecoration(
              border: Border.all(
                color: Theme.of(context).colorScheme.primary,
                width: 2,
              ),
              borderRadius: BorderRadius.circular(10),
            ),
            child: Container(
              padding: EdgeInsets.only(left: 6),
              constraints: BoxConstraints(
                minHeight: 50,
              ),
              child: Markdown(
                data: showHint() ? widget.hintText! : controller.text,
                selectable: true,
                shrinkWrap: true,
                onTapLink: (text, href, title) async {
                  if (href == null) return;
                  var url = Uri.parse(href);
                  // After Android 11
                  // https://stackoverflow.com/a/65916394
                  if (await canLaunchUrl(url)) {
                    await launchUrl(url, mode: LaunchMode.externalApplication);
                  } else {
                    launchUrl(url, mode: LaunchMode.externalApplication);
                    print('Could not launch $href');
                  }
                },
              ),
            ),
          ),
        ),
      );

  bool showHint() => widget.hintText != null && controller.text.isEmpty;
}

pyjserv avatar Oct 26 '22 09:10 pyjserv

Hi, the PR #25 should fix your problem. It will be available in the next version

clemenceroumy avatar Oct 28 '22 11:10 clemenceroumy

Hi, thanks for your reply and your beautiful codes.

pyjserv avatar Oct 28 '22 12:10 pyjserv