fluttertextfield

How to show image as part of TextFormField in flutter


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.

enter image description here


Solution

  • You can break down the design into basic Flutter widgets. Here’s a breakdown:

    UI 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: () {},
      ),
    ),
    
    

    Sample Output