Builder pattern might work (not sure if that's the right term).
First define our Function signatures:
typedef TextBuilder = Widget Function(String text);
typedef TextFieldBuilder = Widget Function(TextEditingController, FocusNode);
Those will be used in your DoubleStatetext
...
DoubleStateText(
initialText: 'Initial Text',
textBuilder: (text) => Text(text, style: TextStyle(fontSize: 18)),
textFieldBuilder: (controller, focusNode) =>
TextField(controller: controller, focusNode: focusNode, cursorColor: Colors.green,)
),
... so instead of passing all the args to DoubleStateText
we pass it builders (functions) that wrap the Text
and TextField
with all the args we want. Then DoubleStateText
just calls the builders instead of creating the Text
/TextField
itself.
Changes to DoubleStateText
:
class DoubleStateText extends StatefulWidget {
final String Function()? onGainFocus;
final String? Function(String value)? onLoseFocus;
final String initialText;
// NEW ==================================================
final TextBuilder textBuilder;
// NEW ==================================================
final TextFieldBuilder textFieldBuilder;
final ThemeData? theme;
final InputDecoration? inputDecoration;
final int? maxLines;
final Color? cursorColor;
final EdgeInsets padding;
final TextStyle? textStyle;
const DoubleStateText({
Key? key,
this.onGainFocus,
this.onLoseFocus,
required this.initialText,
required this.textBuilder, // NEW ==================================================
required this.textFieldBuilder, // NEW ==================================================
this.theme,
this.inputDecoration,
this.maxLines,
this.cursorColor,
this.padding = EdgeInsets.zero,
this.textStyle,
}) : super(key: key);
@override
State<DoubleStateText> createState() => _DoubleStateTextState();
}
class _DoubleStateTextState extends State<DoubleStateText> {
bool _isEditing = false;
late final TextEditingController _textController;
late final FocusNode _focusNode;
late final void Function() _onChangeFocus;
@override
void initState() {
super.initState();
_textController = TextEditingController(text: widget.initialText);
_focusNode = FocusNode();
// handle Enter key event when the TextField is focused
_focusNode.onKeyEvent = (node, event) {
if (event.logicalKey == LogicalKeyboardKey.enter) {
setState(() {
String? text = widget.onLoseFocus?.call(_textController.text);
_textController.text = text ?? widget.initialText;
_isEditing = false;
});
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
};
// handle TextField lose focus event due to other reasons
_onChangeFocus = () {
if (_focusNode.hasFocus) {
String? text = widget.onGainFocus?.call();
_textController.text = text ?? widget.initialText;
}
if (!_focusNode.hasFocus) {
setState(() {
String? text = widget.onLoseFocus?.call(_textController.text);
_textController.text = text ?? widget.initialText;
_isEditing = false;
});
}
};
_focusNode.addListener(_onChangeFocus);
}
@override
void dispose() {
_textController.dispose();
_focusNode.removeListener(_onChangeFocus);
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
Widget child;
if (!_isEditing) {
child = InkWell(
onTap: () {
setState(() {
_isEditing = true;
_focusNode.requestFocus();
});
},
//child: Text(_textController.text, style: widget.textStyle),
// NEW: use the builders ==========================================
child: widget.textBuilder(_textController.text));
} else {
// NEW: use the builders ==========================================
child = widget.textFieldBuilder(_textController, _focusNode);
/*child = TextField(
focusNode: _focusNode,
controller: _textController,
decoration: widget.inputDecoration,
maxLines: widget.maxLines,
cursorColor: widget.cursorColor,
);*/
}
child = Padding(
padding: widget.padding,
child: child,
);
child = Theme(
data: widget.theme ?? Theme.of(context),
child: child,
);
return child;
}
}
Here's an example of how the above would be used:
typedef TextBuilder = Widget Function(String text);
typedef TextFieldBuilder = Widget Function(TextEditingController, FocusNode);
class CompositeWidgetContent extends StatefulWidget {
@override
State<CompositeWidgetContent> createState() => _CompositeWidgetContentState();
}
class _CompositeWidgetContentState extends State<CompositeWidgetContent> {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SomeOtherFocusable(),
SizedBox(
height: 20,
),
DoubleStateText(
initialText: 'Initial Text',
textBuilder: (text) =>
Text(text, style: TextStyle(fontSize: 18)),
textFieldBuilder: (controller, focusNode) => TextField(
controller: controller,
focusNode: focusNode,
cursorColor: Colors.green,
)),
],
),
),
);
}
}
Notice that the (text)
and (controller, focusNode)
are not defined anywhere in _CompositeWidgetContentState
.
Those are not created/used by the end-user/client.
Those are created within DoubleStateText
.