How to Implement SMS Verification Codes Auto-Fill in React Native Android Apps

https://github.com/akvelon/react-native-sms-user-consent

React Native SMS User Consent
React Native SMS User Consent

Problem overview

On one of my projects, I was tasked with implementing the two-factor auth flow which included the auto-fill feature. I encountered the problem that React Native doesn’t support such a feature out-of-box for Android. So I searched for 3rd-party solutions and found only one. It worked in general, but was unstable and led to several app crashes. Also, I could see some architectural issues in it. Considering this, rather than devoting countless hours trying to debug it, my team decided to implement a solution on our own. I implemented it and made the solution an open-source library, so anyone who encounters the same problem can just use it instead of spending their resources unnecessarily doing the exact same work.

This article discusses my experience developing the solution, as well as my experience creating an open-source NPM package for React Native.

The Solution

If you just want to solve the problem without digging into details, you can jump to the library at GitHub and use the solution. For those of you who are interested in some more info and tech details, please continue reading.

Part 1 — Our Approach

How to Start

In order to implement a React Native Android module, a developer has to have experience with native Android and Java/Kotlin. Also, for the SMS User Consent module, they need to study the official tutorial (the overview and the implementation) that explains how it works and how to use it.

Want to read this story later? Save it in Journal.

Additionally, to upload the solution to NPM, we will use the NPM CLI and a package builder. This process is described in Part 2 of this tutorial.

Here we will learn how the module was implemented. It’s considered that it was implemented within an app as opposed to as an external package.

Step 1 — Setting Up the Module

I added main files (ReactNativeSmsUserConsentModule.java and ReactNativeSmsUserConsentPackage.java) following React Native’s official tutorial. In our case, these files live in this android/src/main/java/com/akvelon/reactnativesmsuserconsent folder as well as other implementation files. ReactNativeSmsUserConsentModule.java is the main file that aggregates functionality. We will cover some of its methods later in the tutorial.

Step 2 — Implementing the Native Android Part

Next, I implemented the core SMS User Consent functionality following the official Android tutorial, excluding step 1 since we didn’t need the user’s phone number for our purposes. After following the tutorial, we have the solution that shows the SMS User Consent modal and receives the SMS text.

To view the actual code I ended up with, please see the subscribe method of ReactNativeSmsUserConsentModule.java, which also involves SmsBroadcastReceiver.java and SmsListener.java files that are part of the implementation.

Now we have the core of our solution, but we need some more control and convenience for real-life purposes. There are a couple more methods addressing this:

  • unsubscribe. Removes the listener and stops showing SMS User Consent modal when the SMS is received. This is a crucial method that covers such cases as when the user left the code entry screen before the SMS is received.
  • resubscribe. Since the subscribe method basically handles only one SMS and not any more, we need to unsubscribe and subscribe again every time in order to receive another one. This method handles this, making it a bit easier and safer because there is no space for errors like not calling unsubscribe and only calling subscribe and so on. Also, it catches exceptions in these two methods making it even safer. Furthermore, resubscribe is called automatically when an SMS is handled, which we will examine in the next step, so users of the package don’t need to care about re-subscribing after every single SMS. This is a huge convenience that simplifies life for the React side of the question. This, in particular, makes the final API dramatically simpler.

Step 3 — Connecting Native with React and Creating Wrapper Methods

To expose our methods to the React side we will create methods that are annotated by @ReactMethod following the official React Native guide. The methods are: startNativeSmsListener and stopNativeSmsListener. They are nothing but wrapped subscribe and unsubscribe methods respectfully that use promises as a way of passing data to the React side.

Now we have a way to start and stop listening to SMS from the React side, but we don’t have a way of getting the SMS content when the SMS is received. Let’s add such a way. We receive the SMS text in SmsListener.java. So we create the handleSms method in ReactNativeSmsUserConsentModule.java and call it from the SmsListener.java with the SMS text as the parameter. This is how our main class receives the SMS text. After that, we emit the event using React Native’s standard native event emitter from the sendSmsEventToJs method which is created specifically for this purpose, and this is how the React side gets the SMS text. Also within the handleSms method we call resubscribe so that our code is insta-ready to receive another SMS with a verification code.

Here is how the native part looks:

React Native SMS User Consent — native flow diagram
React Native SMS User Consent — native flow diagram
React Native SMS User Consent — native flow diagram

Now, on the JS-side, we use these methods to form the actual API. Basically, it can be any file. In our package, the API lives in the src/ folder. Let’s shed light on key files in it:

  • index.android.js. Android-only entry point that aggregates all the API methods and exports them.
  • index.ios.js. iOS-only entry point. Since the package is Android-only, we export no-op methods from here, handling iOS for users so that they won’t have to write additional code to handle iOS.
  • nativeApi.js. The core file, which is basically the bridge connecting the Native part with the JS part. It imports NativeEventEmitter and NativeModules objects, handles them, and exports ReactNativeSmsUserConsent (the file containing native methods we exposed using @ReactMethod) and eventEmitter objects that are used by other files.
  • startSmsHandling.js. Another core file that imports ReactNativeSmsUserConsent from nativeApi.js and wraps its startNativeSmsListener and stopNativeSmsListener methods to use in JavaScript.

Now, with the startSmsHandling API method we created, users can add the auto-fill functionality to their apps:

Though this is still not optimal, we’ll examine this in the next (the last) step.

Step 4 — Implementing Convenience Methods

We provided the startSmsHandling method to users which is totally sufficient to add the auto-fill feature to their 2FA screen, but we still can make the usage even easier.

There are two things:

  1. Since the SMS User Consent API provides the whole SMS text and we only need a code from it, it’s a must-have function to parse the code from the SMS (retrieveVerificationCode in the example). And instead of making our users implement it, we implement it for them and provide it as a util method so that they won’t have to bother with it. Users can pass a second argument that specifies the code length, which is 6 by default.
  2. Finally, having all the enhancements we made on native and JS parts, we can leverage code that users have to write to a single line. Since the usage is quite predictable and there is not much space for variability, we can abstract the entire boilerplate by implementing a React hook that controls everything and just provides the verification code once it is received. Actually, this is exactly what I was looking for when searching for a solution, because it’s the most convenient and easy way for a user. This is what useSmsUserConsent hook is. It provides all the quite huge functionality we implemented in previous steps and compresses all the complexity into one simple React hook call:
    const code = useSmsUserConsent();

And now, the package can be used in the following way:

Here is a simple diagram showing the flow:

React Native SMS User Consent flow diagram
React Native SMS User Consent flow diagram

Part 2 — How to Market

In this part, we will examine how the solution was prepared and published as an NPM package. This can also be treated as a general guide for how to create a React Native NPM package.

Step 1 — Create a Dummy React Native Package

For this, we can use either react-native-builder-bob or create-react-native-module as stated in React Native docs. For this project, I used “create-react-native-module” and followed its “Example module with no view” guide. It worked just fine, except for one thing. After generating the package and trying to install it to the test app, I encountered an issue that caused the app to crash. Using the --example-file-linkage param while generating the package resolved the issue.

So, “create-react-native-module” generated a minimal package that can already be uploaded to NPM and used. It added all the necessary files, including package.json, Android build config and dummy implementation files on native and JS side.

Step 2 — Add the Implementation

“create-react-native-module” builder created basic native files in the android/src/main/java/com/akvelon/reactnativesmsuserconsent directory for us. This is where we put our Android-side implementation that we covered in Part 1 of this article. Make sure that for every file in this directory, you specified the correct package name that matches the folder structure, like:

package com.akvelon.reactnativesmsuserconsent;

I recommend editing files in Android Studio so that you can see all the warnings it provides and fix them.

Once we added the native files, we should add the React side files. index.js is the entry point, so we add js files based on it. One more thing is to make sure we populated all the needed fields in package.json. For details, please see package.json docs.

After we have added all the implementation files and populated meta-information, we can test the package by installing it to the example app. Considering that the example app is located in a folder inside our package, the command for installation is:

yarn install file:../

This is a good point of time to provide QA and make sure everything works when you use the installed package. Also, it’s a good idea to write an informative README file that describes how to use your package, its API, etc.

After that, the package is ready to publish.

Step 3 — Publication

Publication is the easiest thing in this tutorial, it consists of three simple steps:

  1. Register on NPM if you’re not registered already;
  2. From the terminal, run npm login and enter your NPM credentials;
  3. Run npm publish —-access public command from the root of your package.

To publish further versions, use the npm version command and then run npm publish.

Once the package is published, you can test it by installing the uploaded package from NPM. Also, it is a good idea to have its source code on GitHub, so that users could learn it, ask questions, open issues, make contributions, etc.

What’s Next?

In this article, I tried to share my experience of creating a React Native SMS User Consent NPM package. I hope it helps people who are interested in learning more details about SMS code auto-fill implementation and publishing to NPM. And of course, I hope the package itself will help many developers who are looking for a solution to the SMS-code auto-fill problem.

Now I am in maintenance mode, looking forward to feedback, and ready to help or answer questions, which can be asked in comments.

About us

At Akvelon Inc, we love working with cutting-edge technologies in mobile development, blockchain, big data, machine learning, artificial intelligence, computer vision, and many others. This article is the result of one of many projects developed in our Office Strategy Labs where we’re testing new technologies and approaches before delivering them to our clients.

Akvelon company official logo
Akvelon company official logo
Akvelon company official logo

If you would like to work with our strong Akvelon team — please see our open positions.

Written in collaboration with Nail Shakirov.

📝 Save this story in Journal.

Frontend & React Native developer at Akvelon, Inc.