In our fast-paced world, SMS messages remain a vital means of communication, even with the rise of chat apps.
With Flutter, you can create beautiful, cross-platform applications, and today we’ll explore how to set your Flutter app as the default SMS application on Android. And it will be described the way how to receive the SMS from our app, the first step of the default SMS app.
Our tutorial starts by creating a new Flutter project using the command line:
flutter create sms_app
To begin, it is essential to include the SMS permission in the AndroidManifest.xml file. As you know, in Android access to sensitive resources such as SMS, storage, and media requires explicit user consent.
For the first, add RECEIVE_SMS permission in the manifest section of the AndroidManifest.xml file.
This permission is necessary for our app to receive incoming sms and this prompts Google Play about the permission used in our app.
<uses-permission android:name="android.permission.RECEIVE_SMS" />
What is the next step?
It is essential to register receivers and activities where we can receive sms, handle and compose for sending new sms in our app.
<receiver android:name=".SmsReceiver" android:permission="android.permission.BROADCAST_SMS" android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
<receiver android:name=".IncomingSmsReceiver" android:permission="android.permission.BROADCAST_SMS" android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<!-- BroadcastReceiver that listens for incoming MMS messages -->
<receiver android:name=".MmsReceiver" android:permission="android.permission.BROADCAST_WAP_PUSH" android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<!-- Activity that allows the user to send new SMS/MMS messages -->
<activity android:name=".ComposeSmsActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</activity>
<!-- Service that delivers messages from the phone "quick response" -->
<service android:name=".HeadlessSmsSendService" android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
Here we registered 2 receivers, 1 activity and 1 service and each of these is for receiving sms, mms and composing new sms.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | |
<uses-permission android:name="android.permission.INTERNET" /> | |
<uses-permission android:name="android.permission.RECEIVE_SMS" /> | |
<queries> | |
<intent> | |
<action android:name="android.intent.action.VIEW" /> | |
<data android:scheme="sms" /> | |
</intent> | |
</queries> | |
<application | |
android:label="sms" | |
android:name="${applicationName}" | |
android:icon="@mipmap/ic_launcher"> | |
<activity | |
android:name=".MainActivity" | |
android:exported="true" | |
android:launchMode="singleTop" | |
android:taskAffinity="" | |
android:theme="@style/LaunchTheme" | |
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | |
android:hardwareAccelerated="true" | |
android:windowSoftInputMode="adjustResize"> | |
<!-- Specifies an Android theme to apply to this Activity as soon as | |
the Android process has started. This theme is visible to the user | |
while the Flutter UI initializes. After that, this theme continues | |
to determine the Window background behind the Flutter UI. --> | |
<meta-data | |
android:name="io.flutter.embedding.android.NormalTheme" | |
android:resource="@style/NormalTheme" | |
/> | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN"/> | |
<category android:name="android.intent.category.LAUNCHER"/> | |
</intent-filter> | |
</activity> | |
<!-- Don't delete the meta-data below. | |
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> | |
<meta-data | |
android:name="flutterEmbedding" | |
android:value="2" /> | |
<receiver android:name=".SmsReceiver" android:permission="android.permission.BROADCAST_SMS" android:exported="true"> | |
<intent-filter> | |
<action android:name="android.provider.Telephony.SMS_DELIVER" /> | |
</intent-filter> | |
</receiver> | |
<receiver android:name=".IncomingSmsReceiver" android:permission="android.permission.BROADCAST_SMS" android:exported="true"> | |
<intent-filter> | |
<action android:name="android.provider.Telephony.SMS_RECEIVED" /> | |
</intent-filter> | |
</receiver> | |
<!-- BroadcastReceiver that listens for incoming MMS messages --> | |
<receiver android:name=".MmsReceiver" android:permission="android.permission.BROADCAST_WAP_PUSH" android:exported="true"> | |
<intent-filter> | |
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" /> | |
<data android:mimeType="application/vnd.wap.mms-message" /> | |
</intent-filter> | |
</receiver> | |
<!-- Activity that allows the user to send new SMS/MMS messages --> | |
<activity android:name=".ComposeSmsActivity" android:exported="true"> | |
<intent-filter> | |
<action android:name="android.intent.action.SEND" /> | |
<action android:name="android.intent.action.SENDTO" /> | |
<category android:name="android.intent.category.DEFAULT" /> | |
<category android:name="android.intent.category.BROWSABLE" /> | |
<data android:scheme="sms" /> | |
<data android:scheme="smsto" /> | |
<data android:scheme="mms" /> | |
<data android:scheme="mmsto" /> | |
</intent-filter> | |
</activity> | |
<!-- Service that delivers messages from the phone "quick response" --> | |
<service android:name=".HeadlessSmsSendService" android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" android:exported="true"> | |
<intent-filter> | |
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" /> | |
<category android:name="android.intent.category.DEFAULT" /> | |
<data android:scheme="sms" /> | |
<data android:scheme="smsto" /> | |
<data android:scheme="mms" /> | |
<data android:scheme="mmsto" /> | |
</intent-filter> | |
</service> | |
</application> | |
<!-- Required to query activities that can process text, see: | |
https://developer.android.com/training/package-visibility and | |
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. | |
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> | |
<queries> | |
<intent> | |
<action android:name="android.intent.action.PROCESS_TEXT"/> | |
<data android:mimeType="text/plain"/> | |
</intent> | |
</queries> | |
</manifest> |
Now we are at the step of composing MainActivity.kt file to set our app as a default SMS app. This is required because the SMS receiving function is only available if the user sets our app as a default SMS app.
Here we use MethodChannel class of Flutter plugin for Android to ensure communication between dart code and native Android code.
In native Android code, we register the channel where we wrote the kotlin code to set our app as a default SMS app and the dart code calls this channel function.
When the app is opened, the entry point is in the Dart code (main.dart), which calls this method channel to requests Android to prompt the native Android modal asking the user to set the app as the default SMS app.
package com.example.sms | |
import android.app.role.RoleManager | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.Intent | |
import android.content.IntentFilter | |
import android.os.Build | |
import android.provider.Telephony | |
import androidx.annotation.NonNull | |
import com.google.gson.JsonObject | |
import io.flutter.embedding.android.FlutterActivity | |
import io.flutter.embedding.engine.FlutterEngine | |
import io.flutter.plugin.common.EventChannel | |
import io.flutter.plugin.common.MethodChannel | |
import android.widget.Toast | |
class MainActivity : FlutterActivity() { | |
private val CHANNEL = "com.example.sms/chat"; | |
var flutterResult: MethodChannel.Result? = null | |
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { | |
if (requestCode == 12) { | |
flutterResult!!.success(resultCode); | |
} | |
} | |
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { | |
super.configureFlutterEngine(flutterEngine) | |
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> | |
flutterResult = result; | |
if (call.method == "setDefaultSms") { | |
try { | |
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { | |
val roleManager: RoleManager = getSystemService(RoleManager::class.java); | |
var intent = roleManager.createRequestRoleIntent (RoleManager.ROLE_SMS); | |
var res = startActivityForResult(intent, 12); | |
} else { | |
var intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT); | |
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, "ccom.example.sms"); | |
startActivity(intent); | |
} | |
} catch (ex: Exception) { | |
result.error("UNAVAILABLE", "Setting default sms.", null); | |
} | |
} else { | |
result.notImplemented(); | |
} | |
} | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
// Make sure to unregister the receiver to avoid leaks | |
// unregisterReceiver(smsReceiver) | |
} | |
} |
import 'package:flutter/services.dart'; | |
import 'package:flutter/material.dart'; | |
void main() async { | |
WidgetsFlutterBinding.ensureInitialized(); | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatefulWidget { | |
const MyApp({super.key}); | |
@override | |
State<StatefulWidget> createState() { | |
return _MyApp(); | |
} | |
} | |
class _MyApp extends State<MyApp> { | |
static const platform = MethodChannel("com.example.sms/chat"); | |
Future<void> setDefaultSMSApp() async { | |
try { | |
platform.invokeMethod('setDefaultSms'); | |
} on PlatformException catch (e) { | |
print("Error: $e"); | |
} | |
} | |
@override | |
void initState() { | |
super.initState(); | |
setDefaultSMSApp(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: Container( | |
decoration: const BoxDecoration(color: Colors.white), | |
), | |
); | |
} | |
} |
Finally, we jump to the step of composing the SmsReceiver.kt file to receive SMS in the background. As we configured SmsReceiver class as a receiver intent for the BROADCAST_SMS permission in AndroidManifest.xml file this class inherits BroadcastReceiver class which is a Base class for code that receives and handles broadcast intents sent by Context.sendBroadcast(Intent).
package com.example.sms | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.Intent | |
import android.os.Build | |
import android.provider.Settings.Secure | |
import android.provider.Telephony | |
import android.widget.Toast | |
import androidx.core.content.ContextCompat.getSystemService | |
import com.google.gson.JsonObject | |
class SmsReceiver : BroadcastReceiver() { | |
override fun onReceive(context: Context?, intent: Intent?) { | |
try { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
for (sms in Telephony.Sms.Intents.getMessagesFromIntent(intent)) { | |
System.out.println("SMS from Receiver:" + sms.displayMessageBody); | |
Toast.makeText(context, "${sms.displayMessageBody}", Toast.LENGTH_SHORT).show() | |
} | |
} | |
} catch (e: Exception) { | |
} | |
} | |
} |
In conclusion, setting up a default SMS app in Flutter is a straightforward process that can greatly enhance the functionality of your mobile application.
By leveraging the available packages and best practices above, you can ensure a seamless and efficient messaging experience for your users.
Thanks for reading!!!
Top comments (0)