<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohammad Omidvar</title>
    <description>The latest articles on DEV Community by Mohammad Omidvar (@momt99).</description>
    <link>https://dev.to/momt99</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F583559%2F2b7737cb-e583-4b8b-af17-48712c0c35ee.jpeg</url>
      <title>DEV Community: Mohammad Omidvar</title>
      <link>https://dev.to/momt99</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/momt99"/>
    <language>en</language>
    <item>
      <title>Add Thousand-Separators for Number Inputs in Jetpack Compose</title>
      <dc:creator>Mohammad Omidvar</dc:creator>
      <pubDate>Fri, 08 Oct 2021 10:33:10 +0000</pubDate>
      <link>https://dev.to/momt99/add-thousand-separators-for-number-inputs-in-jetpack-compose-2650</link>
      <guid>https://dev.to/momt99/add-thousand-separators-for-number-inputs-in-jetpack-compose-2650</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In most financial apps, numerical text fields play a significant role. People want to register their orders precisely in these input boxes, and a consistent experience is vital. One of the basic necessities for these fields is thousand-separators inserted between the numbers while the user is typing. The main challenge is usually keeping the cursor in its correct position and preventing annoying cursor jumps.&lt;/p&gt;

&lt;p&gt;Thanks to the new &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/text/input/VisualTransformation"&gt;&lt;code&gt;VisualTransformation&lt;/code&gt;&lt;/a&gt; option added to the &lt;code&gt;TextField&lt;/code&gt;s in Jetpack Compose, this task has gotten easier than traditional approaches using &lt;code&gt;EditText&lt;/code&gt;. In this post, I have explained the result of migrating my custom &lt;code&gt;EditText&lt;/code&gt;, which is meant for currencies in a cryptocurrency trader app, to the new facilities provided in Jetpack Compose.&lt;/p&gt;

&lt;p&gt;In a nutshell, what happens in the new &lt;code&gt;TextField&lt;/code&gt; is that it keeps the value input by the user, called original text, behind the scenes but displays a transformed version of it using the transformer. As a result, a &lt;code&gt;VisualTransformation&lt;/code&gt; must consist of two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Converting the real value into the desired format that is going to be displayed.&lt;/li&gt;
&lt;li&gt;Translating each position in the original text to the transformed text and vice-versa. In this case, the component is able to place the cursor in the right position.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both these jobs should take place in &lt;code&gt;filter&lt;/code&gt; method, and the return value is of type &lt;code&gt;TransformedText&lt;/code&gt;, which consists of the transformed text and the offset mapping.&lt;/p&gt;

&lt;p&gt;A set of examples can be found in the &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/text/input/VisualTransformation"&gt;official document&lt;/a&gt;, and a great set of credit card transformations is here. &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/benyam7" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a02Q8KO9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--V00XCUU6--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/329592/700a8fb5-0962-4503-8cc7-c50224b8d813.jpg" alt="benyam7"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/benyam7/formatting-credit-card-number-input-in-jetpack-compose-android-2nal" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Formatting credit card number input in Jetpack compose Android&lt;/h2&gt;
      &lt;h3&gt;Benyam ・ Aug 31 ・ 5 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#android&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kotlin&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#compose&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#text&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  ThousandSeparatorVisualTransformation
&lt;/h2&gt;

&lt;p&gt;Let's see how to implement a thousand-separator. In the beginning, I want to mention that as the implementation was meant to be working in a financial app, it has support for decimal places.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the separators
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Regex
&lt;/h4&gt;

&lt;p&gt;A trivial approach would be a loop traversing the text in the reverse order and adding commas after every three characters. However, as I like regex too much, I use this pattern that selects the positions appropriate for thousand separators too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\B(?=(?:\d{3})+(?!\d))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's explain this simple pattern with the help of &lt;a href="https://regex101.com/r/hhJnAF/1"&gt;regex101&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;\d{3}&lt;/code&gt; means three digits. I'm not interested in capturing them (you will find out why), so marking it as a non-capturing group, &lt;code&gt;(?:\d{3})&lt;/code&gt;. Therefore, &lt;code&gt;(?:\d{3})+&lt;/code&gt; will match groups of characters made of digits with a count that is a multitude of three.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(?!\d)&lt;/code&gt; is a negative lookahead. It matches an anchor point that is not a number, like the end of the text or a dot. Along with the previous group, it means the string of numbers with a length equal to a multitude of three and ending where the number ends. So it will not capture every triple in the middle of the number.&lt;/li&gt;
&lt;li&gt;Finally, a positive lookahead matches the positions that the pattern holds true. In our case, the places to put thousand-separators.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\B&lt;/code&gt; ensures that last place is not included. We don't need a separator at the start of the number.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Converting the Integer Part
&lt;/h4&gt;

&lt;p&gt;With the pattern, the rest of the work is quite straightforward. We use &lt;code&gt;replace&lt;/code&gt; method on strings to put the commas in between.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AnnotatedString&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;TransformedText&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;commaReplacementPattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""\B(?=(?:\d{3})+(?!\d))"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;transformed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commaReplacementPattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But further considerations should be given. Localization is one of the essential parts of your app if it is going to be multilingual. So, instead of directly putting a hard-coded comma, we use &lt;code&gt;DecimalFormatSymbols&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;symbols&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DecimalFormat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;decimalFormatSymbols&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;comma&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupingSeparator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  Why not using &lt;code&gt;DecimalFormat&lt;/code&gt;?
&lt;/h5&gt;

&lt;p&gt;In addition to the fact that this class requires parsing before formatting, there are some cases that it doesn't work well. For example, users may want to add several zeroes and then put a number at the left. In that case, the formatter clears all the meaningless zeroes, which deteriorates users' ​convenience.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decimal Places
&lt;/h3&gt;

&lt;p&gt;As mentioned before, in many currencies, a fraction part is available. Also, we know that this part has limited decimal places, e.g., USD can only have two decimal places. So our transformer takes two parameters, &lt;code&gt;maxFractionDigits&lt;/code&gt; and &lt;code&gt;minFractionDigits&lt;/code&gt; (for enforced decimal places). It ends up with this part in the code.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;zero&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeroDigit&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;minFractionDigits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxFractionDigits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minFractionDigits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fracPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxFractionDigits&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minFractionDigits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Furthermore, there is an edge case, when the input has only a fraction part, so the user starts it with a dot. In this case, our strategy is to put a zero at the start of the text for better readability.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Ensure there is at least one zero for integer places&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;normalizedIntPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;intPart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  All Together
&lt;/h3&gt;

&lt;p&gt;Connecting the described pieces will end to the following code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* As android uses icu in the recent versions we just let DecimalFormat to
​* take care of the selection
​*/&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;symbols&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DecimalFormat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;decimalFormatSymbols&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;commaReplacementPattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""\B(?=(?:\d{3})+(?!\d))"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AnnotatedString&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;TransformedText&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;comma&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupingSeparator&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decimalSeparator&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;zero&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeroDigit&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;intPart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;fracPart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="c1"&gt;//Ensure there is at least one zero for integer places&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;normalizedIntPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;intPart&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;integersWithComma&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalizedIntPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commaReplacementPattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;minFractionDigits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxFractionDigits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minFractionDigits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minFractionDigits&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;fracPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;

       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fracPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxFractionDigits&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minFractionDigits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnnotatedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;integersWithComma&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fracPart&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;".$fracPart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spanStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paragraphStyles&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Translation of Offsets
&lt;/h3&gt;

&lt;p&gt;After the transformation is done, we need to implement two offset mapping functions described above. The main idea here is correctly counting the added characters before a position. I'm not going to the details as they are pretty straightforward, and I suffice to the code provided below.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;inner&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ThousandSeparatorOffsetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;originalIntegerLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;transformedIntegersLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;transformedLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;commaIndices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;addedLeadingZero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OffsetMapping&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;commaCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calcCommaCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originalIntegerLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;leadingZeroOffset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addedLeadingZero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;originalToTransformed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="c1"&gt;// Adding number of commas behind the character&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;originalIntegerLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;originalIntegerLength&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxFractionDigits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;transformedLength&lt;/span&gt;
           &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
               &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;commaCount&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;leadingZeroOffset&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
           &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commaCount&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;calcCommaCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originalIntegerLength&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;transformedToOriginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="c1"&gt;// Subtracting number of commas behind the character&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;transformedIntegersLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;commaCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transformedLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;leadingZeroOffset&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
           &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;commaIndices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;takeWhile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;calcCommaCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intDigitCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
       &lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;intDigitCount&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--37anL4wV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://gist.githubusercontent.com/momt99/5e756f227ff3a813b5870260453fdb73/raw/2e3f4d5b955bcb34153854fb4c18354119d0f109/demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--37anL4wV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://gist.githubusercontent.com/momt99/5e756f227ff3a813b5870260453fdb73/raw/2e3f4d5b955bcb34153854fb4c18354119d0f109/demo.gif" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, you can check out the full implementation.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



</description>
      <category>android</category>
      <category>compose</category>
      <category>kotlin</category>
      <category>textfield</category>
    </item>
  </channel>
</rss>
