I want to show image as part of TextFormField
in flutter. Like how it is shown in ChatGPT. I should be able to delete that image by pressing backspace. I don't want to add the image to the prefix or suffix as suggested in many cases. I want it to be a part of the chat.
I came across rich text editors like flutter quill but I am not sure how good it will be. Is there any other way to achieve the same?
UPDATE: I am looking for something like the below image . But instead of close icon using backspace to clear the image would be better.
You can break down the design into basic Flutter widgets. Here’s a breakdown:
Also, to remove the images one by one on backspace events, we can use the Focus
widget to capture key events, specifically listening for the backspace key.
Here is my implementation of this custom widget:
class CustomTextFormField extends StatefulWidget {
final TextFormField textFormField;
final List<Widget> images;
final Widget? button;
CustomTextFormField({
super.key,
required this.textFormField,
this.button,
this.images = const [],
}) : assert(
textFormField.controller != null,
'textFormField must have a controller',
);
@override
State<CustomTextFormField> createState() => _CustomTextFormFieldState();
}
class _CustomTextFormFieldState extends State<CustomTextFormField> {
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomRight,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Display images if available
if (widget.images.isNotEmpty) ...[
SizedBox(
height: 80,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: widget.images.length,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (_, index) => ClipRRect(
borderRadius: BorderRadius.circular(12),
child: widget.images[index],
),
),
),
const SizedBox(height: 8),
],
Theme(
data: Theme.of(context).copyWith(
inputDecorationTheme: InputDecorationTheme(
border: InputBorder.none,
contentPadding: widget.button != null
? const EdgeInsets.only(right: 42)
: EdgeInsets.zero,
),
),
// The TextFormField wrapped with Focus to listen for backspace
child: Focus(
onKeyEvent: (_, event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.backspace) {
final text = widget.textFormField.controller!.text.trim();
// Remove the last image when backspace is pressed and input is empty
if (text.isEmpty & widget.images.isNotEmpty) {
setState(() => widget.images.removeLast());
}
}
return KeyEventResult.ignored;
},
child: widget.textFormField,
),
),
],
),
),
// Optional button (e.g., mic icon) if provided
if (widget.button != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: widget.button,
),
],
);
}
}
Then our CustomTextFormField
can be used like this:
CustomTextFormField(
textFormField: TextFormField(
minLines: 1,
maxLines: 6,
controller: TextEditingController(),
decoration: const InputDecoration(
hintText: 'Message',
),
),
images: List.generate(
3,
(index) => Image.network(
'https://picsum.photos/100?random=$index',
width: 80,
height: 80,
fit: BoxFit.cover,
),
),
button: IconButton(
icon: const Icon(Icons.mic),
onPressed: () {},
),
),