Skip to content

Commit 896c19a

Browse files
authored
Characters docs (flutter#67361)
1 parent b5562c1 commit 896c19a

File tree

7 files changed

+55
-4
lines changed

7 files changed

+55
-4
lines changed

dev/integration_tests/flutter_gallery/lib/demo/material/tabs_demo.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
1414
class _Page {
1515
_Page({ this.label });
1616
final String label;
17-
String get id => label[0];
17+
String get id => label.characters.first;
1818
@override
1919
String toString() => '$runtimeType("$label")';
2020
}

packages/flutter/lib/src/cupertino/text_field.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe
131131
/// field (e.g., by pressing a button on the soft keyboard), the text field
132132
/// calls the [onSubmitted] callback.
133133
///
134+
/// {@macro flutter.widgets.editableText.complexCharacters}
135+
///
134136
/// To control the text that is displayed in the text field, use the
135137
/// [controller]. For example, to set the initial value of the text field, use
136138
/// a [controller] that already contains some text such as:

packages/flutter/lib/src/material/search.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import 'theme.dart';
4343
/// the [AppBar.leading] position e.g. from the hamburger menu to the back arrow
4444
/// used to exit the search page.
4545
///
46+
/// ## Handling emojis and other complex characters
47+
/// {@macro flutter.widgets.editableText.complexCharacters}
48+
///
4649
/// See also:
4750
///
4851
/// * [SearchDelegate] to define the content of the search page.
@@ -85,6 +88,9 @@ Future<T> showSearch<T>({
8588
/// A given [SearchDelegate] can only be associated with one active [showSearch]
8689
/// call. Call [SearchDelegate.close] before re-using the same delegate instance
8790
/// for another [showSearch] call.
91+
///
92+
/// ## Handling emojis and other complex characters
93+
/// {@macro flutter.widgets.editableText.complexCharacters}
8894
abstract class SearchDelegate<T> {
8995

9096
/// Constructor to be called by subclasses which may specify [searchFieldLabel], [keyboardType] and/or

packages/flutter/lib/src/material/text_field.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
227227
/// builder: (BuildContext context) {
228228
/// return AlertDialog(
229229
/// title: const Text('Thanks!'),
230-
/// content: Text ('You typed "$value".'),
230+
/// content: Text ('You typed "$value", which has length ${value.characters.length}.'),
231231
/// actions: <Widget>[
232232
/// TextButton(
233233
/// onPressed: () { Navigator.pop(context); },
@@ -256,6 +256,14 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
256256
///
257257
/// Keep in mind you can also always read the current string from a TextField's
258258
/// [TextEditingController] using [TextEditingController.text].
259+
///
260+
/// ## Handling emojis and other complex characters
261+
/// {@macro flutter.widgets.editableText.complexCharacters}
262+
///
263+
/// In the live Dartpad example above, try typing the emoji 👨‍👩‍👦
264+
/// into the field and submitting. Because the example code measures the length
265+
/// with `value.characters.length`, the emoji is correctly counted as a single
266+
/// character.
259267
///
260268
/// See also:
261269
///

packages/flutter/lib/src/services/text_formatter.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import 'text_input.dart';
2525
/// To create custom formatters, extend the [TextInputFormatter] class and
2626
/// implement the [formatEditUpdate] method.
2727
///
28+
/// ## Handling emojis and other complex characters
29+
/// {@macro flutter.widgets.editableText.complexCharacters}
30+
///
2831
/// See also:
2932
///
3033
/// * [EditableText] on which the formatting apply.
@@ -300,13 +303,19 @@ class WhitelistingTextInputFormatter extends FilteringTextInputFormatter {
300303
}
301304

302305
/// A [TextInputFormatter] that prevents the insertion of more characters
303-
/// (currently defined as Unicode scalar values) than allowed.
306+
/// than allowed.
304307
///
305308
/// Since this formatter only prevents new characters from being added to the
306309
/// text, it preserves the existing [TextEditingValue.selection].
307310
///
311+
/// Characters are counted as user-perceived characters using the
312+
/// [characters](https://pub.dev/packages/characters) package, so even complex
313+
/// characters like extended grapheme clusters and surrogate pairs are counted
314+
/// as single characters.
315+
///
316+
/// See also:
308317
/// * [maxLength], which discusses the precise meaning of "number of
309-
/// characters" and how it may differ from the intuitive meaning.
318+
/// characters".
310319
class LengthLimitingTextInputFormatter extends TextInputFormatter {
311320
/// Creates a formatter that prevents the insertion of more characters than a
312321
/// limit.

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,22 @@ class EditableText extends StatefulWidget {
946946
/// {@end-tool}
947947
/// {@endtemplate}
948948
///
949+
/// ## Handling emojis and other complex characters
950+
/// {@template flutter.widgets.editableText.complexCharacters}
951+
/// It's important to always use
952+
/// [characters](https://pub.dev/packages/characters) when dealing with user
953+
/// input text that may contain complex characters. This will ensure that
954+
/// extended grapheme clusters and surrogate pairs are treated as single
955+
/// characters, as they appear to the user.
956+
///
957+
/// For example, when finding the length of some user input, use
958+
/// `string.characters.length`. Do NOT use `string.length` or even
959+
/// `string.runes.length`. For the complex character "👨‍👩‍👦", this
960+
/// appears to the user as a single character, and `string.characters.length`
961+
/// intuitively returns 1. On the other hand, `string.length` returns 8, and
962+
/// `string.runes.length` returns 5!
963+
/// {@endtemplate}
964+
///
949965
/// See also:
950966
///
951967
/// * [inputFormatters], which are called before [onChanged]

packages/flutter/lib/src/widgets/navigator.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,16 @@ class Navigator extends StatefulWidget {
15711571
/// The [NavigatorState] and [initialRoute] will be passed to the callback.
15721572
/// The callback must return a list of [Route] objects with which the history
15731573
/// will be primed.
1574+
///
1575+
/// When parsing the initialRoute, if there's any chance that the it may
1576+
/// contain complex characters, it's best to use the
1577+
/// [characters](https://pub.dev/packages/characters) API. This will ensure
1578+
/// that extended grapheme clusters and surrogate pairs are treated as single
1579+
/// characters by the code, the same way that they appear to the user. For
1580+
/// example, the string "👨‍👩‍👦" appears to the user as a single
1581+
/// character and `string.characters.length` intuitively returns 1. On the
1582+
/// other hand, `string.length` returns 8, and `string.runes.length` returns
1583+
/// 5!
15741584
final RouteListFactory onGenerateInitialRoutes;
15751585

15761586
/// Whether this navigator should report route update message back to the

0 commit comments

Comments
 (0)