title: Handling grapheme clusters in Dart
body: <p>From what I can tell Dart does not have support for grapheme clusters, though there is talk of supporting it:</p>
  
  <ul>
  <li><a href="https://github.com/dart-lang/language/issues/34" rel="noreferrer">Dart Strings should support Unicode grapheme cluster operations #34</a></li>
  <li><a href="https://github.com/dart-lang/language/issues/49" rel="noreferrer">Minimal Unicode grapheme cluster support #49</a></li>
  </ul>
  
  <p>Until it is implemented, what are my options for iterating through grapheme clusters? For example, if I have a string like this:</p>
  
  <pre><code>String family = '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}'; // ‍‍
  String myString = 'Let me introduce my $family to you.';
  </code></pre>
  
  <p>and there is a cursor after the five-codepoint family emoji:</p>
  
  <p><a href="https://i.stack.imgur.com/4T8Nl.png" rel="noreferrer"><img src="https://i.stack.imgur.com/4T8Nl.png" alt="enter image description here"></a></p>
  
  <p>How would I move the cursor one user-perceived character to the left? </p>
  
  <p>(In this particular case I know the size of the grapheme cluster so I could do it, but what I am really asking about is finding the length of an arbitrarily long grapheme cluster.)</p>
  
  <p><strong>Update</strong></p>
  
  <p>I see from <a href="https://oleb.net/blog/2017/11/swift-4-strings/" rel="noreferrer">this article</a> that Swift uses the system's <a href="http://site.icu-project.org/" rel="noreferrer">ICU</a> library. Something similar may be possible in Flutter.</p>
  
  <h2>Supplemental code</h2>
  
  <p>For those who want to play around with my example above, here is a demo project. The buttons move the cursor to the right or left. It currently takes 8 button presses to move the cursor past the family emoji.</p>
  
  <p><a href="https://i.stack.imgur.com/KFvbI.png" rel="noreferrer"><img src="https://i.stack.imgur.com/KFvbI.png" alt="enter image description here"></a></p>
  
  <p><em>main.dart</em></p>
  
  <pre><code>import 'package:flutter/material.dart';
  
  void main() =&gt; runApp(MyApp());
  
  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Grapheme cluster testing')),
          body: BodyWidget(),
        ),
      );
    }
  }
  
  class BodyWidget extends StatefulWidget {
    @override
    _BodyWidgetState createState() =&gt; _BodyWidgetState();
  }
  
  class _BodyWidgetState extends State&lt;BodyWidget&gt; {
  
    TextEditingController controller = TextEditingController(
        text: 'Let me introduce my \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467} to you.'
    );
  
    @override
    Widget build(BuildContext context) {
      return Column(
        children: &lt;Widget&gt;[
          TextField(
            controller: controller,
          ),
          Row(
            children: &lt;Widget&gt;[
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: RaisedButton(
                  child: Text('&lt;&lt;'),
                  onPressed: () {
                    _moveCursorLeft();
                  },
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: RaisedButton(
                  child: Text('&gt;&gt;'),
                  onPressed: () {
                    _moveCursorRight();
                  },
                ),
              ),
            ],
          )
        ],
      );
    }
  
    void _moveCursorLeft() {
      int currentCursorPosition = controller.selection.start;
      if (currentCursorPosition == 0)
        return;
      int newPosition = currentCursorPosition - 1;
      controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
    }
  
    void _moveCursorRight() {
      int currentCursorPosition = controller.selection.end;
      if (currentCursorPosition == controller.text.length)
        return;
      int newPosition = currentCursorPosition + 1;
      controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
    }
  }
  </code></pre>