markdown-editable-textinput
markdown-editable-textinput copied to clipboard
The outside controller may not be disposed.
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;
}
Hi, the PR #25 should fix your problem. It will be available in the next version
Hi, thanks for your reply and your beautiful codes.