8

I have a text field in flutter and an emoji picker button. On selecting an emoji I need to insert it at the current cursor position. How can I achieve this? Currently using TextEditingController I'm only able to append the emoji. I'm not able to get the current cursor offset.

emojiPicker() {
    return EmojiPicker(
      rows: 3,
      columns: 7,
      recommendKeywords: null,
      indicatorColor: flujoAccentColor,
      onEmojiSelected: (emoji, category) {
        print(emoji);
        _messageInputController.text =
            _messageInputController.text + emoji.emoji;
     }
    );
  }
krishnakumarcn
  • 3,959
  • 6
  • 39
  • 69

4 Answers4

21
  1. Use _txtController.selection to get the selection (or cursor position).
  2. replace the selection with selected emoji.
  3. then fix the selection(or cursor position) of the controller
import 'package:emoji_picker/emoji_picker.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: HomePage()));
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  TextEditingController _messageInputController;

  @override
  void initState() {
    _messageInputController = TextEditingController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: SafeArea(
        child: Column(
          children: <Widget>[
            EmojiPicker(
              rows: 3,
              columns: 7,
              recommendKeywords: null,
              indicatorColor: Colors.red,
              onEmojiSelected: (emoji, category) {
                String text = _messageInputController.text;
                TextSelection textSelection = _messageInputController.selection;
                String newText = text.replaceRange(
                    textSelection.start, textSelection.end, emoji.emoji);
                final emojiLength = emoji.emoji.length;
                _messageInputController.text = newText;
                _messageInputController.selection = textSelection.copyWith(
                  baseOffset: textSelection.start + emojiLength,
                  extentOffset: textSelection.start + emojiLength,
                );
              },
            ),
            TextField(
              controller: _messageInputController,
            ),
          ],
        ),
      ),
    );
  }
}

with this you can not only insert the selected emoji at cursor position, but also can replace some selected text

Crazy Lazy Cat
  • 13,595
  • 4
  • 30
  • 54
  • For displaying the emoji picker, first I'm manually hiding the keyboard by focusing on some other focus node. So the textselection start will be -1. that gives me a range error on text.replaceRange. – krishnakumarcn Feb 04 '20 at 16:41
  • I also tried by inserting the emoji at position returned by textediting controller listener on selection.start but the issue is since my text field is not on focus it is returning -1. How to make the text field focus and show the external emoji picker also without displaying the keyboard. – krishnakumarcn Feb 04 '20 at 16:45
  • 1
    When you are manually hiding the keyboard, before hiding it, capture the current point and store it in a variable. Then you'll have the last position before it was closed? – loganrussell48 Sep 11 '20 at 14:36
  • Thank you! This answer saved me a lot of time. – Suragch Sep 14 '20 at 23:44
  • 1
    This worked fine! I also have to suggest another solution if the position of the cursor is -1. I did the following: `var cursorPos = textController.selection.base.offset; ` `if(cursorPos < 0) { textController.text += emoji.toString(); } ` – we_mor Feb 02 '21 at 14:14
8

This is a slight modification to CrazyLazyCat's answer.

void _insertText(String inserted) {
  final text = _controller.text;
  final selection = _controller.selection;
  final newText = text.replaceRange(selection.start, selection.end, inserted);
  _controller.value = TextEditingValue(
    text: newText,
    selection: TextSelection.collapsed(offset: selection.baseOffset + inserted.length),
  );
}

Notes:

  • _controller is a TextEditingController.
  • If you are changing both the text and the selection then you should use a TextEditingValue rather than changing them individually (since they each trigger an update).
  • Generally if you insert something you want the cursor to appear after then insert, thus the TextSelection.collapsed with the adjusted index.
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • 1
    I like this approach. Though @we_more's comment on CrazyLazyCat's answer is still valid: selection.start can be `-1` so we should check `base.offset` and just append the text in that case. – KYL3R Aug 12 '21 at 17:46
1

i have another solution beside text.replaceRange.

All you need is :

TextEditingController _tec;

String youWillAddtoTEC = "your emoji or your clipboard data or else";

String beforeCursorPositionAtTEC = tec.selection.textBefore(tec.text);

String afterCursorPositionAtTEC = tec.selection.textAfter(tec.text);

String result = beforeCursorPositionAtTEC + youWillAddtoTEC + afterCursorPositionAtTEC;

and then add result to tec, or any widget your need:

tec.text = result;

for the selection or cursor position is same with above, but if you need place cursor after the "youWillAddToTEC" you can do like this:

tec.selection = TextSelection.collapsed(offset: tec.selection.start + youWillAddtoTEC.lenght);
Sayyid J
  • 1,215
  • 1
  • 4
  • 18
0

If you want to replace the selection of text field with a new string, I found the method below is useful.

void replaceTextSelectionWith(TextEditingController textEditingController, Function(String selection) getReplaceString)
{
  final text = textEditingController.text;
  final selection = textEditingController.selection;
  final replaceText = getReplaceString(selection.textInside(text)) as String;
  final newText = text.replaceRange(selection.start, selection.end, replaceText);

  textEditingController.value =
      TextEditingValue(text: newText, selection: TextSelection.collapsed(offset: selection.start + replaceText.length));
}

And use it like this

replaceTextSelectionWith(textEditingController, (selection) => '**$selection**');