o7planning

Format Credit Card Number with Android TextWatcher

  1. Credit Card Number Pattern
  2. Example: TextWatcher Credit Card Number

1. Credit Card Number Pattern

Card Type
The issuing network
Length
Number of allowable digits
IIN Ranges
The card number will always begin with ...
Spacing Patterns
How the digits and spaces are printed on the physical credit cards
Visa (incl. VPay)
13-19
4
#### #### #### ####
(4-4-4-4)
Pattern not known for 13-15 and 17-19 digit cards.
Visa Electron
16
4026, 417500, 4405, 4508, 4844, 4913, 4917
#### #### #### ####
(4-4-4-4)
American Express
15
34, 37
#### ###### #####
(4-6-5)
MasterCard
16
51‑55,
222100‑272099
#### #### #### ####
(4-4-4-4)
Diners Club Carte Blanche
14
300‑305
#### ###### ####
(4-6-4)
Diners Club International
14
300‑305,
309, 36,
38‑39
#### ###### ####
(4-6-4)
Diners Club United States & Canada
16
54, 55
#### #### #### ####
(4-4-4-4)
Discover
16
6011,
622126‑622925,
644‑649,
65
#### #### #### ####
(4-4-4-4)
JCB
16
3528‑3589
#### #### #### ####
(4-4-4-4)
UATP
15
1
#### ##### ######
(4-5-6)
Dankort
16
5019
#### #### #### ####
(4-4-4-4)
InterPayment
16-19
636
#### #### #### ####
(4-4-4-4)
Pattern not known for 17-19 digit cards.
Maestro
12-19
500000‑509999,
560000‑589999,
600000‑699999
#### #### ##### (4-4-5)
#### ###### ##### (4-6-5)
#### #### #### #### (4-4-4-4)
#### #### #### #### ### (4-4-4-4-3)
Pattern not known for 12, 14, 17, and 18 digit cards.
China UnionPay
16-19
62
#### #### #### #### (4-4-4-4)
###### ############# (6-13)
Pattern not known for 17-18 digit cards.

2. Example: TextWatcher Credit Card Number

In this example, the CreditCardNumberTextWatcher class will be based on the first characters that the user enters in EditText to detect the Credit Card type:
Next, based on the Credit Card type, it formats the card number that the user is entering:
Visa Electron Credit Card Type. (####-####-####-####) (4-4-4-4)
UATP Credit Card Type. (####-#####-######) (4-5-6)
Alright, on Android Studio, create a Project:
  • File > New > New Project > Empty Activity
    • Name: TextWatcherCreditCardExample
    • Package name: org.o7planning.textwatchercreditcardexample
    • Language: Java
Copy a few Images into the drawable folder of the project:
CreditCardType.java
package org.o7planning.textwatchercreditcardexample;

import java.util.Arrays;

public enum CreditCardType {
    // 4
    VISA("Visa", "icon_visa",
            13, 19,
            new int[]{4},
            new int[]{4,4,4,4,4}
    ),
    // 4026, 417500, 4405, 4508, 4844, 4913, 4917
    VISA_ELECTRON("Visa Electron", "icon_visa_electron",
            16, 16,
            new int[]{4026, 417500, 4405, 4508, 4844, 4913, 4917},
            new int[]{4,4,4,4}
    ),
    // 34, 37
    AMERICAN_EXPRESS("American Express", "icon_american_express",
            15, 15,
            new int[]{34, 37},
            new int[]{4,6,5}
    ),
    // 51‑55, 222100‑272099
    MASTERCARD("MasterCard", "icon_mastercard",
            16, 16,
            concat(intRange(51,55), intRange(222100,272099)),
            new int[]{4,4,4,4}
    ),
    // 6011, 622126‑622925, 644‑649, 65
    DISCOVER("MasterCard", "icon_mastercard",
            16, 16,
            append(concat(intRange(622126,622925), intRange(644,649)),6011,65),
            new int[]{4,4,4,4}
    ),
    // 3528‑3589
    JCB("JCB", "icon_jcb",
            16, 16,
             intRange(3528,3589),
            new int[]{4,4,4,4}
    ),
    // 1
    UATP("UATP", "icon_uatp",
            15, 15,
            new int[]{1},
            new int[]{4,5,6}
    ),
    // 5019
    DANKORT("Dankort", "icon_dankort",
            16, 16,
            new int[]{5019},
            new int[]{4,4,4,4}
    );

    public static final int[] DEFAULT_BLOCK_LENGTHS = new int[] {4, 4, 4, 4, 4};
    public static final int DEFAULT_MAX_LENGTH = 4 * 5 ;

    private String[] prefixs;
    private int[] blockLengths;
    private String name;
    // Name of Image in "drawable" folder.
    private String imageResourceName;

    private int minLength;
    private int maxLength;

    CreditCardType(String name, String imageResourceName,
                    int minLength, int maxLength,
                    int[] intPrefixs, int[] blockLengths)  {
        this.name = name;
        this.imageResourceName = imageResourceName;
        if(intPrefixs!= null)  {
            this.prefixs = new String[intPrefixs.length];
            for(int i=0;i< intPrefixs.length;i++) {
                this.prefixs[i] = String.valueOf(intPrefixs[i]);
            }
        }
        this.minLength = minLength;
        this.maxLength = maxLength;
        this.blockLengths = blockLengths;
    }

    public String getName() {
        return name;
    }

    public int getMinLength() {
        return minLength;
    }

    public int getMaxLength() {
        return maxLength;
    }
    public String[] getPrefixs()  {
        return this.prefixs;
    }

    public int[] getBlockLengths()  {
        return this.blockLengths;
    }

    public String getImageResourceName()  {
        return this.imageResourceName;
    }

    private static int[] intRange(int from, int to)  {
        int length = to - from + 1;
        int[] ret = new int[length];
        for(int i= from; i < to + 1; i++) {
            ret[i-from] = i;
        }
        return ret;
    }

    private static int[] concat(int[] first, int[] second)  {
        int[] both = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, both, first.length, second.length);
        return both;
    }

    private static int[] append(int[] first, int ... value)  {
        if(value == null || value.length == 0)  {
            return first;
        }
        int[] both = Arrays.copyOf(first, first.length + value.length);

        for(int i = 0; i < value.length; i++) {
            both[first.length + i] = value[i];
        }
        return both;
    }

    public static CreditCardType detect(String creditCardNumber)  {
        if(creditCardNumber == null || creditCardNumber.isEmpty())  {
            return null;
        }
        CreditCardType found = null;
        int max = 0;
        for(CreditCardType type: CreditCardType.values()) {
            for(String prefix : type.prefixs) {
                if(creditCardNumber.startsWith(prefix) && prefix.length() > max) {
                    found = type;
                    max = prefix.length();
                }
            }
        }
        return found;
    }

}
CreditCardNumberTextWatcher.java
package org.o7planning.textwatchercreditcardexample;

import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;

public class CreditCardNumberTextWatcher implements TextWatcher {

    private static final String LOG_TAG = "AndroidExample";
    public static final char SEPARATOR = '-';

    private EditText editText;

    private int after;
    private String beforeString;

    public CreditCardNumberTextWatcher(EditText editText) {
        this.editText = editText;
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        this.after = after;
        this.beforeString = s.toString();
        Log.e(LOG_TAG, "@@beforeTextChanged s=" + s
                + " . start="+ start+" . after=" + after+" . count="+ count);
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        Log.e(LOG_TAG, "@@onTextChanged s=" + s
                + " . start="+ start+" . before=" + before+" . count="+ count);
        String newText = s.toString();

        String textPrefix = newText.substring(0, start);
        String textInserted = newText.substring(start, start + this.after);
        String textSuffix = newText.substring(start + this.after);
        String textBeforeCursor = textPrefix + textInserted;

        // User delete the SEPARATOR.
        if(this.after == 0 && count == 0 && beforeString.charAt(start) == SEPARATOR)  {
            if(start > 0)  {
                textPrefix = textPrefix.substring(0, textPrefix.length() -1);
            }
        }

        // Non-digit
        String regex = "[^\\d]";

        String textPrefixClean = textPrefix.replaceAll(regex, "");
        String textInsertedClean = textInserted.replaceAll(regex, "");
        String textSuffixClean = textSuffix.replaceAll(regex, "");
        String textBeforeCursorClean = textPrefixClean + textInsertedClean;

        // creditCardNumber
        String newTextClean = textPrefixClean + textInsertedClean + textSuffixClean;

        CreditCardType creditCardType = this.showDetectedCreditCardImage(newTextClean);

        int[] blockLengths = CreditCardType.DEFAULT_BLOCK_LENGTHS; // {4,4,4,4,4}
        int minLength = 0;
        int maxLength = CreditCardType.DEFAULT_MAX_LENGTH; // 4*5

        if(creditCardType != null)  {
            blockLengths = creditCardType.getBlockLengths();
            minLength = creditCardType.getMinLength();
            maxLength = creditCardType.getMaxLength();
        }
        Log.i(LOG_TAG, "newTextClean= " + newTextClean);


        int[] separatorIndexs = new int[blockLengths.length];
        for(int i=0; i < separatorIndexs.length; i++) {
            if(i==0)  {
                separatorIndexs[i] = blockLengths[i];
            } else {
                separatorIndexs[i] = blockLengths[i] + separatorIndexs[i-1];
            }
        }
        Log.i(LOG_TAG, "blockLengths= " + this.toString(blockLengths));
        Log.i(LOG_TAG, "separatorIndexs= " +  this.toString(separatorIndexs));

        int cursorPosition = start + this.after - textBeforeCursor.length() + textBeforeCursorClean.length();

        StringBuilder sb = new StringBuilder();
        int separatorCount = 0;
        int cursorPositionDelta = 0;
        int LOOP_MAX = Math.min(newTextClean.length(), maxLength);

        for(int i = 0; i < LOOP_MAX; i++) {
            sb.append(newTextClean.charAt(i));

            if(this.contains(separatorIndexs,i + 1) && i < LOOP_MAX - 1) {
                sb.append(SEPARATOR);
                separatorCount++;
                if(i < cursorPosition) {
                    cursorPositionDelta++;
                }
            }
        }
        cursorPosition= cursorPosition + cursorPositionDelta;

        String textFormatted = sb.toString();
        if(cursorPosition > textFormatted.length()) {
            cursorPosition =  textFormatted.length();
        }

        this.editText.removeTextChangedListener(this);
        this.editText.setText(textFormatted);
        this.editText.addTextChangedListener(this);
        this.editText.setSelection(cursorPosition);
    }

    @Override
    public void afterTextChanged(Editable s) {

    }

    private String toString(int[] array)  {
        StringBuilder sb= new StringBuilder();
        for(int i=0;i< array.length;i++) {
            if(i == 0) {
                sb.append("[").append(array[i]);
            } else {
                sb.append(", ").append(array[i]);
            }
        }
        sb.append("]");
        return sb.toString();
    }

    private boolean contains(int[] values, int value)  {
        for(int i=0;i<values.length;i++) {
            if(values[i] == value) {
                return true;
            }
        }
        return false;
    }


    private CreditCardType showDetectedCreditCardImage(String creditCardNumber)  {
        CreditCardType type = CreditCardType.detect(creditCardNumber);

        if(type != null)  {
            Drawable icon = ResourceUtils.getDrawableByName(this.editText.getContext(), type.getImageResourceName());
            this.editText.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
        } else {
            Drawable icon = ResourceUtils.getDrawableByName(this.editText.getContext(), "icon_none");
            this.editText.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
        }
        return type;
    }
}
ResourceUtils.java
package org.o7planning.textwatchercreditcardexample;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;

public class ResourceUtils {

    public static Drawable getDrawableByName(Context context, String resourceName)  {
        Resources resources = context.getResources();
        final int resourceId = resources.getIdentifier(resourceName, "drawable",
                context.getPackageName());
        return resources.getDrawable(resourceId);
    }
}
Here is the interface of the example:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView81"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginRight="16dp"
        android:text="Enter Your Credit Card Number:"
        android:textSize="20sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editText_creditCardNumber"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:drawableRight="@drawable/icon_none"
        android:ems="10"
        android:inputType="phone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView81" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.o7planning.textwatchercreditcardexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextWatcher;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {

    private EditText editTextCreditCardNumber;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.editTextCreditCardNumber = (EditText) this.findViewById(R.id.editText_creditCardNumber);

        TextWatcher textWatcher = new CreditCardNumberTextWatcher(this.editTextCreditCardNumber);
        this.editTextCreditCardNumber.addTextChangedListener(textWatcher);
    }
}

Android Programming Tutorials

Show More