Automatically Adding Dashes To Text For Input Field In Android

Jesseosile
4 min readFeb 21, 2024

--

Hey, everyone! In today’s article, we’ll be discussing how to add dashes to numbers after every 2 digits, like so: “24–27–21” in Android.

Recently, I had a task that seemed super easy at first, but when I got down to it, it wasn’t as straightforward as I thought. It took a whole lot of thinking and digging to resolve.

I was tasked with building an app that scans a QR code, retrieves some data, and displays it in a fancy manner — super easy, right? 😁 The app begins with a splash screen and then transitions to a login screen, which includes a form. Herein lies our problem. The form required a special code, first name, and last name for login. Our focus — The special code.

The special code looked something like this: “24–32–21”. I had to ensure that when the user was entering the code, a “-” was added after every 2 digits until the end of the code. The only characters allowed were numbers and dashes. Once the user had entered a valid code, nothing else was allowed to be entered into the edit text.

THE PROBLEM

Writing code to format the input to “24-” after every 2 digits wasn’t much of a hassle. However, integrating this code with the EditText posed a challenge. Initially, I created a function called formatEditTextInput, which took a string (the user input). This function handled formatting the digits to the special code and then sent this formatted value back to the special code’s EditText for display. I called this function from the EditText’s addTextChangedListener.

I thought that was it until I ran the app, entered a value into the special code’s EditText, and the app crashed 🥲; this happened for a while.

So what happened? I had used DataBinding to link my EditText’s text in the XML to a LiveData variable for the special code inside the ViewModel. Whenever formatEditTextInput was called (even if only one digit was entered), the digit would be analyzed, formatted if needed, and then the LiveData’s value would be reset from the ViewModel.

However, this meant that if the LiveData’s value changed automatically, the EditText’s text would also change, leading to another call to the textChangedListener lambda (resulting in another call to formatEditTextInput), and this became an infinite loop until the app crashed 😂.

A little research on Google and Stack Overflow helped me cook up an answer.

THE SOLUTION

Before delving into the solution, you’ll need to have knowledge in a couple of concepts:

  • Basics of building Android apps
  • ViewModels
  • LiveData
  • Hilt Dependency Injection

Much of what I initially did remained unchanged; I made only a couple of adjustments.

I have a function called formatSpecialCodeText in my ViewModel that takes input from the EditText and appends a “-” to the given text based on specific conditions.

Additionally, there’s a function called formatEditTextInput in the MainActivity, which takes an Editable from the EditText. It performs some formatting on the text and then sends it to the ViewModel for further processing before setting the EditText’s text to the newly formatted string.

This function also checks if the user has already entered a valid special code. If they haven't, the aforementioned process occurs; otherwise, it ensures that the user is not allowed to enter any more text.

Now, the game-changer: Rather than using the EditText’s addTextChangedListener lambda, I opted for the EditText’s addTextChangedListener function that takes a TextWatcher as a parameter.

WHAT IS A TEXT WATCHER?

Essentially, a TextWatcher is an object you attach to an EditText that monitors what you type inside the EditText in real time.

I define a variable called textWatcher (of type TextWatcher) and initialize the variable inside the activity’s onCreate callback. The TextWatcher has three functions that must be overridden, but our focus is on the afterTextChanged function. This is where the difference is made.

I ensure to call the EditText’s addTextChangedListener function, passing the TextWatcher to it. Inside the TextWatcher’s afterTextChanged, three things happen:

  1. I first remove the TextWatcher as the Text Changed Listener for the special code EditText.
  2. I then call formatEditTextInput, passing the input text obtained from the afterTextChanged parameter.
  3. Finally, I add the TextWatcher as the Text Changed Listener for the special code EditText again.

WHAT DOES THIS HELP ACHIEVE?

When the user enters a value into the EditText, the TextWatcher's addTextChangedListener gets called. The first line of code ensures that the EditText is no longer not listening, preventing our infinite loop that causes the app to crash.

formatEditTextInput gets called, formatting the entered values as required and then setting the EditText’s text to the formatted value.

The TextWatcher gets added as a Text Changed Listener again, allowing the EditText to listen to text changes in real time.

This process continues until we get a valid special code.

I believe this approach can be helpful in situations where you have to format digits in an EditText, such as adding spaces to digits of a phone number or adding commas to very long numbers (1,000,000), etc.

You can find the full code on Github:

--

--