DEV Community

loading...
Cover image for Automatic SMS Verification With Android's SMS Retrieval API

Automatic SMS Verification With Android's SMS Retrieval API

Kwabena Bio Berko
Software Engineer
・5 min read

We, as Android developers always strive to make our app's onboarding process of new users as friction-less as possible. One way to achieve such smooth, effortless on-boarding is to implement an Automatic SMS verification feature.

If you've used Uber or Whatsapp, you would have noticed that their SMS verification is automatic: The user need not manually type in the verification code. This makes their on-boarding feel seamless and kind of effortless on the part of the user. And the best part of this is that they do not request the user's SMS permission.😮 This is done using Google's SMS Retrieval API, and in this article, we will see how to achieve just that in our apps as well.

Before we begin, we need to import the following libraries:

  1. Google Play Services Auth API - This library contains the SMS Retrieval API classes.

  2. Apache Commons - This will be used to parse out the verification code from the SMS message.

  3. EventBus - We will be using a BroadcastReceiver to listen for the retrieved SMS from the SMS Retrieval API. EventBus is a library that uses the publisher/subscriber pattern and will be used to simplify the communication between our BroadcastReceiver and our Activity classes.

Let's add them to our app's build.gradle file:

    implementation 'com.google.android.gms:play-services-auth:19.0.0'
    implementation 'org.apache.commons:commons-lang3:3.11'
    implementation 'org.greenrobot:eventbus:3.2.0'
Enter fullscreen mode Exit fullscreen mode

Alright, we are set.

Our XML layout file is extremely simple. It contains a TextView in the middle of the screen. This view will be used to display the verification code we extract using the SMS Retrieval API.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/otp_text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="@android:color/black"
        android:textSize="30sp"
        android:gravity="center"
        android:text="Loading..."
        />

</RelativeLayout>
Enter fullscreen mode Exit fullscreen mode

What we are going to do next is to get an instance of the SmsRetrieverClient object, call its startSmsRetriever instance method, and attach onSuccess and onFailure Listeners to the resulting Task. I'm going to wrap that portion of code in a method to easily reuse later:

private fun startSmsListener() {
    smsRetrieverClient.startSmsRetriever()
        .addOnSuccessListener {
            Toast.makeText(this@MainActivity, "Starting Sms Retriever", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener { e ->
            e.printStackTrace()
            Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
        }
}
Enter fullscreen mode Exit fullscreen mode

Then we call that method immediately after onCreate:

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var smsRetrieverClient: SmsRetrieverClient
    private lateinit var otpTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        otpTextView = findViewById(R.id.otp_text_view)
        smsRetrieverClient = SmsRetriever.getClient(this)
        startSmsListener()
    }
...

Enter fullscreen mode Exit fullscreen mode

At this point, when an SMS message is received on our device, the Play Services library we added earlier will broadcast an SmsRetriever.SMS_RETRIEVED_ACTION intent to our app. This intent contains the status of the background processing as well as the text of the SMS message.
Let's create a BroadcastReceiver class to handle this:

SmsBroadcastReceiver.kt

class SmsBroadcastReceiver : BroadcastReceiver() {

    companion object {
        val TAG = SmsBroadcastReceiver::class.java.simpleName
    }

    override fun onReceive(context: Context?, intent: Intent) {
        Log.d(TAG, "onReceive Called!")
        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
            val bundle = intent.extras
            if (bundle != null) {
                val status: Status = bundle[SmsRetriever.EXTRA_STATUS] as Status
                var isTimeout = false
                var smsMessage: String? = null
                when (status.statusCode) {
                    CommonStatusCodes.SUCCESS -> {
                        val message =
                            bundle[SmsRetriever.EXTRA_SMS_MESSAGE] as String
                        Log.d(TAG, message)
                        smsMessage = message
                    }
                    CommonStatusCodes.TIMEOUT -> {
                        Log.d(TAG, "Timeout")
                        isTimeout = true
                    }
                }
                EventBus.getDefault().post(SmsRetrievedEvent(isTimeout, smsMessage))
            }
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

In the BroadcastReceiver's onReceive method, we first check the Status of the background processing done by the SMS Retrieval API. We also create an instance of a class called SmsRetrievedEvent. This is our event class that will be published by EventBus to our Subscriber.The SmsRetrievedEvent class is nothing but a simple Kotlin data class. Kindly read more here if you are totally new to EventBus.

SmsRetrievedEvent.kt

data class SmsRetrievedEvent(
    val isTimeout: Boolean,
    val smsMessage: String?
)
Enter fullscreen mode Exit fullscreen mode

If the background processing was successful, we set the message property of the SmsRetrievedEvent class to the SMS message we've retrieved. Otherwise, we set timeout to true if a timeout occurred. We finally publish the event to any listening subscriber.

Note: Timeouts occurs if messages are not handled within 5 minutes after processing by the SMS Retreival API.

Let's not forget to register this BroadcastReceiver in our AndroidManifest file:

AndroidManifest.xml


<application>
        ...
        <receiver
            android:name=".SmsBroadcastReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
            </intent-filter>
        </receiver>
    </application>

Enter fullscreen mode Exit fullscreen mode

Next, we are going to register, unregister as well as implement our subscriber in our MainActivity class. This method will be defined with the @Subscribe annotation and will be called when an event is published/posted.

Note: Always remember to unregister your subscribers to avoid memory leak issues.

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var smsRetrieverClient: SmsRetrieverClient
    private lateinit var otpTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        otpTextView = findViewById(R.id.otp_text_view)
        smsRetrieverClient = SmsRetriever.getClient(this)
        startSmsListener()
    }


    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
    }

    override fun onStop() {
        EventBus.getDefault().unregister(this)
        super.onStop()
    }

    @Subscribe
    fun onEvent(event: SmsRetrievedEvent) {
    }

    private fun startSmsListener() {
        smsRetrieverClient.startSmsRetriever()
            .addOnSuccessListener {
                Toast.makeText(this@MainActivity, "Starting Sms Retriever", Toast.LENGTH_SHORT).show()
            }.addOnFailureListener { e ->
                e.printStackTrace()
                Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
            }
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, we parse out the 4 digit verification code from the message using the substringAfterLast method from the Apache Commons library, and display it in our TextView

...
@Subscribe
    fun onEvent(event: SmsRetrievedEvent) {
        Log.d("SMS EVENT", "RECEIVED SMS EVENT")
        Log.d("SMS EVENT - Timeout=", event.isTimeout.toString())
        Log.d("SMS EVENT - Message=", if(!event.isTimeout) event.smsMessage!! else "Timeout: No Message")

        val otp: String =
            StringUtils.substringAfterLast(event.smsMessage, "is").replace(":", "")
                .trim().substring(0, 4)
        Log.d("EVENT", otp)

        runOnUiThread {
            otpTextView.text = if (!event.isTimeout) otp else "Timeout :("
        }

        startSmsListener()
    }
...
Enter fullscreen mode Exit fullscreen mode

One important thing to keep in the back of our minds:
The SMS Retreival API requires that SMS messages be in the following format:

Your verification code is 9872


FA+9qCX9VSu

Enter fullscreen mode Exit fullscreen mode

The SMS message must end with an 11-character hash string that identifies your app(which is FA+9qCX9VSu in this case). Click here to see how to compute your app's hash.

At this point we can send the SMS message to our device, extract and send the 4 digit code to our server for verification.

Click here to see a sample project on GitHub.

Happy Coding, everyone!

Discussion (0)