Handle incoming messages on the device with Flutter.

Aayush Sharma
DSC KIET
Published in
8 min readFeb 11, 2022

Hey, Flutter devs!! It’s been about 2 years since I started working with flutter and by far I’ve come across various great challenges which expanded my knowledge in Flutter. One such use case was reading incoming messages on the user’s device.

Before we ramble into the code-verse lemme explain how I came across this challenge. I’ve been working on a project that involves user authentication by the means of an OTP. The challenge was to automatically read the OTP from the device and update the UI.

Many of you must be aware of some packages that provide this functionality like sms_autofill, but their implementation is very narrow and only applicable only in certain cases.

I myself started with a package to achieve this functionality but for this to work, you need the app signature to be included in the message and in my case, it was not possible. What if your use-case was to read the messages that include financial transactions or something else, In such cases, these packages don’t work. If you’ve ever stumbled upon such challenges, then this article is for you, Read along😊

Overview🧐

In this article, we’ll learn how to read incoming messages on a user’s device. We will be using platform channels to communicate with the native side and write some platform-specific code for Android in Kotlin.

Prerequisite:- Minimal knowledge of flutter/Dart would suffice for you to survive through this article.

Base Project

You can refer to the link given here for the base project. The base UI of the project is given below:-

Base UI of the project

Whenever any SMS is received by the device we will update it on the device. Also, refer to the Github repo for code reference of the complete project.

Now let’s get started🚀

Platform channels, Method calls, and much more

According to the official Flutter documentation, Platform channels are used to pass messages between client and host platform (Android or IoS). In simpler words, it provides a bridge between dart code and platform-specific code.

On the client side, MethodChannel enables sending messages that correspond to method calls. On the platform side, MethodChannel on Android (MethodChannelAndroid) and FlutterMethodChannel on iOS (MethodChanneliOS) enable receiving method calls and sending back a result.

Method calls are basically functions that perform some calculation on the host side and return a result to the client(again a message).

We will be focussing only on the Android part for this article, not IoS.

How do we trigger a method call?🤔

Triggering method calls are done asynchronously in Flutter. We do something like:

Now the result returned by the method call can be a future or a Broadcast Stream depending upon the Platform channel used. There are three types of platform channels available. We will be using mainly two of them, they’re:-

  1. MethodChannel (returns future)
  2. EventChannel (returns BroadcastStream)

Summarizing things

We make platform channels on the client-side (Flutter) and the host side (android/ios). Then we can use that channel to invoke a method call asynchronously. This method call executes a block of platform-specific code on Android/ios. After successful completion of the method call, it returns the result back to the client-side.

Let’s dive into some code👨‍💻

Let’s create a separate file in the lib folder. In this file, all the code related to our platform channel will live. Create a new class and declare a method channel for receiving SMS.

class PlatformChannel {
static const _channel = MethodChannel("com.example.app/sms")
}

This is how you declare a method channel. MethodChannel the class takes in a string as a required parameter that defines its name. Now we’ll have to declare a method channel with the same name on the android side.

I’d recommend using Android studio for writing Kotlin code if you’ve no prior experience with Kotlin like me😅. Open the android folder of your flutter application in android studio and navigate to MainActivity.kt .

In MainActivity.kt inside the MainActivity class, we’ll override the configureFlutterEngine function. Inside this function, we’ll initialize our method channel and call setMethodCallHandler the method provided in MethodChannel class.

The method channel on the android side takes in a binary messenger provided by the flutter engine which is used to pass the messages to the client-side. setMethodCallHandler gives us two things — call and result.

call is used to identify which method call has been triggered from the dart code. result is used to return back a response to the dart code, it may be successful/unsuccessful/exception. In the same block it can be achieved as follows:-

Now let’s switch to the flutter side for a moment to trigger this method call and understand the bigger picture more clearly.

In the PlatformChannel class, create a new function to receive the SMS.

We call invokeMethod method on _channel which returns a result asynchronously. As you can see the string “receive_sms” passed in invokeMethod determines the method call on both sides. Now all that's left is to write the logic to read SMS in Kotlin. So let’s do that quickly!

Reading SMS ✉

In Android, all the data related to phone operation, specifically SMS and MMS messages can be accessed by using the Telephony class. An incoming message can be read from the device through a BroadcastReceiver whenever SMS_RECEIVED_ACTION or a DATA_SMS_RECEIVED_ACTION intent is received.

Now, what are BroadcastReceiver and an intent?

Let's start with intent, According to the Android docs, Intents are messaging objects. It has three fundamental use-cases and in our case whenever any system event occurs an intent carrying the data about it is passed to our BroadcastReceiver. Specifically, In our case, we accept only those intents which are either SMS_RECEIVED_ACTION or a DATA_SMS_RECEIVED_ACTION.

You would’ve guessed by now that a BroadcastReceiver is just a handler for system events.

Let's implement it!

In the methodCallHandler for the method “receive_sms” declare the smsReceiver object of type BroadcastReceiver as follows:-

The onReceive function handles all the broadcast messages from system events. Now we have to get the message from these intents. To do this, We have a method inside the Telephony.Sms.Intents class called getMessageFromIntent which takes in intent as a parameter. It returns an array of SmsMessage class.

The SmsMessage class provides a method displayMessageBody that provides the body of the SMS received. To get this, Inside the onReceive function we can do something like:-

You would’ve got the basic idea from the code about what we’re actually trying to do. result.success() passes the sms.displayMessageBody back to the dart code.

Before it works, We need to register the BroadcastReceiver and unregister it when we’ve successfully received an SMS. To do this we have a function called registerReceiver which takes in the broadcast receiver i.e smsReceiver and an intent filter which will be — “android.provider.Telephony.SMS_RECEIVED”

Anddd, We’re not done yet🌚

Finally, we need to add the required permissions to the manifest and also ask the user for SMS permission.

Add the following line above the application tag in the android manifest file.

<uses-permission android:name="android.permission.RECEIVE_SMS" />

Add the permission_handler plugin to request SMS permission from the user. we can create a function to request SMS permission.

Then inside the initState of our app do this:-

Anddd… We’re finally done. Run the app and send an SMS to the device it is running on.

On an emulator, you can send SMS by clicking on the three dots given at the bottom of the panel on the right. Click on the phone section and there you can simulate sending SMS.

Well, We’ve successfully accomplished our objective But what if you wanted to keep reading SMS continuously?. In that case, we need something like a stream of SMS. Don’t worry Flutter has you covered by giving you EventChannels.

Setting up a stream of SMS

Instead of a method channel, we need to set up event channels on both sides to set up a stream of messages. In the PlatformChannel class let’s recreate our channel as given below.

static const _channel = EventChannel("com.example.app/smsStream");

We can now create a method that returns the stream of messages. To to that flutter provides you with a method called receiveBroadcastStream on an event channel.

In the main.dart file, inside our initState we will change the function to update SMS as follows:-

That’s all on the flutter side. Now, all we need to do is set up an event channel on the Kotlin side and somehow create a stream of messages.

In Kotlin

navigate to MainActivity.kt and inside the configureFlutterEngine function create an EventChannel and call setStreamHandler method on it.

Now, setStreamHandler the method takes a parameter of the type EventChannel.StreamHandler. So, we need to make a few changes to our smsReceiver object. We need it to implement the EventChannel.StreamHandler interface as one would do in Kotlin.

We can do it as follows:-

Now apart from onReceive function, We have onListen and onCancel functions as well. These are provided by the EventChannel.StreamHandler interface. It’s the same as when you need to override functions of a parent abstract class by a child class that implements it.

Now you would notice that we do not have any call/result kind of thing as we did in handling the method call earlier. Instead, we get something called an EventSink to communicate messages to the dart code in form of a stream.

Above the onListen function, we’ll declare an EventSink.

var eventSink: EventChannel.EventSink? = null

Inside the onListen function, Initialize it to the value of the variable events provided by the function itself.

eventSink = events

In the onCancel function, we can make ur eventSink null

eventSink = null

Finally, to emit messages t the dart code, we have a method called success on the eventSink just like we had on the result object.

eventSink?.success(sms.displayMessageBody)

Now the smsReceiver object looks like the following piece of code:-

Again we need to register our smsReceiver to make it work. But this time we do not unregister it.

registerReceiver(smsReceiver,IntentFilter("android.provider.Telephony.SMS_RECEIVED"))

And that’s it. Now run your app and it will read messages continuously from the device.

Concluding things

In this article, we learned to read an incoming SMS from the device in a very basic way on Android. You can follow up on the official flutter docs to implement it in ios as well and you’ll need to look up how to read SMS in swift as well.

Platform channels let us achieve services and functionalities that are currently not possible in Flutter. You can extend this knowledge in your own way, in your own challenge that you’re facing currently. Like someone would want to read these SMS in the background.

Well, I’ll be ending this article here. I hope you learned something new today😊. You can reach me on LinkedIn in case of any query. I’ll be happy to help.

--

--