Update
Dart team released a helper package that helps achieving this. String.characters.skipLast(1)
should be able to do what you expect.
Old answer
First, let's get to some definitions. According to this page:
- Bytes: 8-bit. The number of bytes that a Unicode string will take up in memory or storage depends on the encoding.
- Code Units: The smallest bit combination that can be used to express a single unit in text encoding. For example 1 code unit in UTF-8 would be 1 byte, 2 bytes in UTF-16, 4 bytes in UTF-32.
- Code Points [or rune]: Unicode character. A single integer value (from U+0000-U+10FFFF) on a Unicode space.
- Grapheme clusters: A single character perceived by the user. 1 grapheme cluster consists of several code points.
When you remove the last char using substring
, you're actually removing the last code unit. If you run print(newStr.codeUnits)
and print(str.codeUnits)
, you'll see that the rune 128512
is equivalent to the joint of the code units 55357
and 56832
, so 55357
is actually valid, but doesn't represent anything without the "help" of another code unit.
In fact, you don't want to use substring()
when there's non-ASCII chars in your String (like emojis or arabic letters). It'll never work. What you have to do is remove the last grapheme cluster. Something as simple as that:
str.graphemeClusters.removeLast()
However, Dart doesn't support this yet. There are several issues around this point. Some of those:https://github.com/dart-lang/language/issues/34
https://github.com/dart-lang/language/issues/49
This lack of support seams to result in some other of issues, like the one you mentioned and this one:https://github.com/flutter/flutter/issues/31818