I want to make user inputted phone number in an editText to dynamically change format every time the user inputs a number. That is, when user inputs up to 4 digits, like 7144, the editText shows "714-4". I would like the editText to be dynamically updated to format ###-###-#### whenever the user inputs a digit. how can this be done? also, I am handling more than one editTexts.
8 Answers
Easiest way to do this is to use the built in Android PhoneNumberFormattingTextWatcher.
So basically you get your EditText in code and set your text watcher like this...
EditText inputField = (EditText) findViewById(R.id.inputfield);
inputField.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
Nice thing about using PhoneNumberFormattingTextWatcher is that it will format your number entry correctly based on your locale.

- 5,362
- 6
- 31
- 51

- 4,516
- 7
- 38
- 45
-
5If you desire a custom mask, you can find this answer useful: http://stackoverflow.com/a/34907607/1013929 – Maurício Fonseca Jan 20 '16 at 18:29
-
*editTextPhone.addTextChangedListener(PhoneNumberFormattingTextWatcher())* **Kotlin** – J A S K I E R Feb 05 '21 at 21:26
-
Without setting the input type to InputType.TYPE_CLASS_PHONE, this will not work, I spent some minutes to find this out – FabioR Sep 02 '22 at 16:33
Above answer is right but it works with country specific. if anyone want such formatted phone number(###-###-####). Then use this:
etPhoneNumber.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
int digits = etPhoneNumber.getText().toString().length();
if (digits > 1)
lastChar = etPhoneNumber.getText().toString().substring(digits-1);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int digits = etPhoneNumber.getText().toString().length();
Log.d("LENGTH",""+digits);
if (!lastChar.equals("-")) {
if (digits == 3 || digits == 7) {
etPhoneNumber.append("-");
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
Declare String lastChar = " "
in your activity.
Now add this line in xml of your edittext
android:inputType="phone"
That's all.
Edited: If you want your edittext lenght to limit 10 digits add line below also:
android:maxLength="12"
(It is 12 because "-" will take space two times)

- 5,949
- 5
- 36
- 46
-
this works for XXX-XXX-XXXX formatting phone numbers. Just copy paste. – karthik prasad Dec 01 '16 at 14:27
Just add the following to EditText for Phone Number to get a formatted phone number(###-###-####)
Phone.addTextChangedListener(new TextWatcher() {
int length_before = 0;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
length_before = s.length();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (length_before < s.length()) {
if (s.length() == 3 || s.length() == 7)
s.append("-");
if (s.length() > 3) {
if (Character.isDigit(s.charAt(3)))
s.insert(3, "-");
}
if (s.length() > 7) {
if (Character.isDigit(s.charAt(7)))
s.insert(7, "-");
}
}
}
});

- 149
- 2
- 4
Dynamic Mask for Android in Kotlin. This one is working fine and strictly fitting the phone number mask. You can provide any mask you whish.
EDIT1: I have a new version that locks event the unwanted chars typed by the user on the keyboard.
/**
* Text watcher allowing strictly a MASK with '#' (example: (###) ###-####
*/
class NumberTextWatcher(private var mask: String) : TextWatcher {
companion object {
const val MASK_CHAR = '#'
}
// simple mutex
private var isCursorRunning = false
private var isDeleting = false
override fun afterTextChanged(s: Editable?) {
if (isCursorRunning || isDeleting) {
return
}
isCursorRunning = true
s?.let {
val onlyDigits = removeMask(it.toString())
it.clear()
it.append(applyMask(mask, onlyDigits))
}
isCursorRunning = false
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
isDeleting = count > after
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
private fun applyMask(mask: String, onlyDigits: String): String {
val maskPlaceholderCharCount = mask.count { it == MASK_CHAR }
var maskCurrentCharIndex = 0
var output = ""
onlyDigits.take(min(maskPlaceholderCharCount, onlyDigits.length)).forEach { c ->
for (i in maskCurrentCharIndex until mask.length) {
if (mask[i] == MASK_CHAR) {
output += c
maskCurrentCharIndex += 1
break
} else {
output += mask[i]
maskCurrentCharIndex = i + 1
}
}
}
return output
}
private fun removeMask(value: String): String {
// extract all the digits from the string
return Regex("\\D+").replace(value, "")
}
}
EDIT 2: Unit tests
class NumberTextWatcherTest {
@Test
fun phone_number_test() {
val phoneNumberMask = "(###) ###-####"
val phoneNumberTextWatcher = NumberTextWatcher(phoneNumberMask)
val input = StringBuilder()
val expectedResult = "(012) 345-6789"
var result = ""
// mimic typing 10 digits
for (i in 0 until 10) {
input.append(i)
result = mimicTextInput(phoneNumberTextWatcher, result, i.toString()) ?: ""
}
Assert.assertEquals(input.toString(), "0123456789")
Assert.assertEquals(result, expectedResult)
}
@Test
fun credit_card_test() {
val creditCardNumberMask = "#### #### #### ####"
val creditCardNumberTextWatcher = NumberTextWatcher(creditCardNumberMask)
val input = StringBuilder()
val expectedResult = "0123 4567 8901 2345"
var result = ""
// mimic typing 16 digits
for (i in 0 until 16) {
val value = i % 10
input.append(value)
result = mimicTextInput(creditCardNumberTextWatcher, result, value.toString()) ?: ""
}
Assert.assertEquals(input.toString(), "0123456789012345")
Assert.assertEquals(result, expectedResult)
}
@Test
fun date_test() {
val dateMask = "####/##/##"
val dateTextWatcher = NumberTextWatcher(dateMask)
val input = "20200504"
val expectedResult = "2020/05/04"
val initialInputValue = ""
val result = mimicTextInput(dateTextWatcher, initialInputValue, input)
Assert.assertEquals(result, expectedResult)
}
@Test
fun credit_card_expiration_date_test() {
val creditCardExpirationDateMask = "##/##"
val creditCardExpirationDateTextWatcher = NumberTextWatcher(creditCardExpirationDateMask)
val input = "1121"
val expectedResult = "11/21"
val initialInputValue = ""
val result = mimicTextInput(creditCardExpirationDateTextWatcher, initialInputValue, input)
Assert.assertEquals(result, expectedResult)
}
private fun mimicTextInput(textWatcher: TextWatcher, initialInputValue: String, input: String): String? {
textWatcher.beforeTextChanged(initialInputValue, initialInputValue.length, initialInputValue.length, input.length + initialInputValue.length)
val newText = initialInputValue + input
textWatcher.onTextChanged(newText, 1, newText.length - 1, 1)
val editable: Editable = SpannableStringBuilder(newText)
textWatcher.afterTextChanged(editable)
return editable.toString()
}
}

- 965
- 12
- 27
My script, example taken from here description here
<android.support.design.widget.TextInputLayout
android:id="@+id/numphone_layout"
app:hintTextAppearance="@style/MyHintText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_text_numphone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/MyEditText"
android:digits="+() 1234567890-"
android:hint="@string/hint_numphone"
android:inputType="phone"
android:maxLength="17"
android:textSize="14sp" />
</android.support.design.widget.TextInputLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextInputEditText phone = (TextInputEditText) findViewById(R.id.edit_text_numphone);
//Add to mask
phone.addTextChangedListener(textWatcher);
}
TextWatcher textWatcher = new TextWatcher() {
private boolean mFormatting; // this is a flag which prevents the stack overflow.
private int mAfter;
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// nothing to do here..
}
//called before the text is changed...
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//nothing to do here...
mAfter = after; // flag to detect backspace..
}
@Override
public void afterTextChanged(Editable s) {
// Make sure to ignore calls to afterTextChanged caused by the work done below
if (!mFormatting) {
mFormatting = true;
// using US or RU formatting...
if(mAfter!=0) // in case back space ain't clicked...
{
String num =s.toString();
String data = PhoneNumberUtils.formatNumber(num, "RU");
if(data!=null)
{
s.clear();
s.append(data);
Log.i("Number", data);//8 (999) 123-45-67 or +7 999 123-45-67
}
}
mFormatting = false;
}
}
};

- 1
- 1

- 69
- 4
The above solutions do not take backspace into consideration so when you delete some numbers after typing, the format tends to mess up. Below code corrects this issue.
phoneNumberEditText.addTextChangedListener(new TextWatcher() {
int beforeLength;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
beforeLength = phoneNumberEditText.length();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int digits = phoneNumberEditText.getText().toString().length();
if (beforeLength < digits && (digits == 3 || digits == 7)) {
phoneNumberEditText.append("-");
}
}
@Override
public void afterTextChanged(Editable s) { }
});

- 21
- 5
This code allow you enter phone number with mask ### - ### - #### (without spaces) and also here is fixed the issue with deletion of phone digits:
editText.addTextChangedListener(new TextWatcher() {
final static String DELIMITER = "-";
String lastChar;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
int digits = editText.getText().toString().length();
if (digits > 1)
lastChar = editText.getText().toString().substring(digits-1);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int digits = editText.getText().length();
// prevent input dash by user
if (digits > 0 && digits != 4 && digits != 8) {
CharSequence last = s.subSequence(digits - 1, digits);
if (last.toString().equals(DELIMITER))
editText.getText().delete(digits - 1, digits);
}
// inset and remove dash
if (digits == 3 || digits == 7) {
if (!lastChar.equals(DELIMITER))
editText.append("-"); // insert a dash
else
editText.getText().delete(digits -1, digits); // delete last digit with a dash
}
dataModel.setPhone(s.toString());
}
@Override
public void afterTextChanged(Editable s) {}
});
Layout:
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:textAlignment="textStart"
android:inputType="number"
android:digits="-0123456789"
android:lines="1"
android:maxLength="12"/>

- 5,229
- 4
- 36
- 62
Here is my solution
How run in Activity/Fragment (f.e in onViewCreated):
//field in class
private val exampleIdValidator by lazy { ExampleIdWatcher(exampleIdField.editText!!) }
exampleIdField.editText?.addTextChangedListener(exampleIdValidator)
Validatior class:
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
class ExampleIdWatcher(exampleIdInput: EditText) : TextWatcher {
private var exampleIdInput: EditText = exampleIdInput
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(userInput: CharSequence?, start: Int, before: Int, count: Int) {
if (userInput!!.isNotEmpty() && !areSpacesCorrect(userInput)) {
val stringTextWithoutWhiteSpaces: String = userInput.toString().replace(" ", "")
val textSB: StringBuilder = StringBuilder(stringTextWithoutWhiteSpaces)
when {
textSB.length > 8 -> {
setSpacesAndCursorPosition(textSB, 2, 6, 10)
}
textSB.length > 5 -> {
setSpacesAndCursorPosition(textSB, 2, 6)
}
textSB.length > 2 -> {
setSpacesAndCursorPosition(textSB, 2)
}
}
}
}
override fun afterTextChanged(s: Editable?) {
}
private fun setSpacesAndCursorPosition(textSB: StringBuilder, vararg ts: Int) {
for (t in ts) // ts is an Array
textSB.insert(t, SPACE_CHAR)
val currentCursorPosition = getCursorPosition(exampleIdInput.selectionStart)
exampleIdInput.setText(textSB.toString())
exampleIdInput.setSelection(currentCursorPosition)
}
private fun getCursorPosition(currentCursorPosition: Int): Int {
return if (EXAMPLE_ID_SPACE_CHAR_CURSOR_POSITIONS.contains(currentCursorPosition)) {
currentCursorPosition + 1
} else {
currentCursorPosition
}
}
private fun areSpacesCorrect(userInput: CharSequence?): Boolean {
EXAMPLE_ID_SPACE_CHAR_INDEXES.forEach {
if (userInput!!.length > it && userInput[it].toString() != SPACE_CHAR) {
return false
}
}
return true
}
companion object {
private val EXAMPLE_ID_SPACE_CHAR_INDEXES: List<Int> = listOf(2, 6, 10)
private val EXAMPLE_ID_SPACE_CHAR_CURSOR_POSITIONS: List<Int> = EXAMPLE_ID_SPACE_CHAR_INDEXES.map { it + 1 }
private const val SPACE_CHAR: String = " "
}
}
Layout:
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:digits=" 0123456789"
android:inputType="numberPassword"
android:maxLength="14"
tools:text="Example text" />
Result is:
XX XXX XXX XXX

- 1
- 1
-
While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. Please also try not to crowd your code with explanatory comments, this reduces the readability of both the code and the explanations! – Waqar UlHaq Feb 20 '20 at 17:51