Sends SMS directly on Android

Addresses issue #57
This commit is contained in:
Dan Silk 2022-02-14 16:55:09 +10:30
parent f68c750b4b
commit d4b2579f90
10 changed files with 92 additions and 22 deletions

2
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Flutter", "name": "Example App",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"program": "example/lib/main.dart" "program": "example/lib/main.dart"

View File

@ -56,6 +56,19 @@ List<String> recipents = ["1234567890", "5556787676"];
_sendSMS(message, recipents); _sendSMS(message, recipents);
``` ```
On Android, you can skip the additional dialog with the sendDirect parameter.
``` dart
String message = "This is a test message!";
List<String> recipents = ["1234567890", "5556787676"];
String _result = await sendSMS(message: message, recipients: recipents, sendDirect: true)
.catchError((onError) {
print(onError);
});
print(_result);
```
## Screenshots ## Screenshots
iOS SMS | Android MMS iOS SMS | Android MMS

View File

@ -1,21 +1,25 @@
package com.example.flutter_sms package com.example.flutter_sms
import android.annotation.TargetApi import android.annotation.TargetApi
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import android.app.Activity import android.app.Activity
import android.net.Uri import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.telephony.SmsManager
import android.util.Log
import androidx.annotation.NonNull import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
private lateinit var mChannel: MethodChannel private lateinit var mChannel: MethodChannel
@ -76,9 +80,10 @@ class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
"A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS.") "A device may be unable to send messages if it does not support messaging or if it is not currently configured to send messages. This only applies to the ability to send text messages via iMessage, SMS, and MMS.")
return return
} }
val message = call.argument<String?>("message") val message = call.argument<String?>("message") ?: ""
val recipients = call.argument<String?>("recipients") val recipients = call.argument<String?>("recipients") ?: ""
sendSMS(result, recipients, message!!) val sendDirect = call.argument<Boolean?>("sendDirect") ?: false
sendSMS(result, recipients, message!!, sendDirect)
} }
"canSendSMS" -> result.success(canSendSMS()) "canSendSMS" -> result.success(canSendSMS())
else -> result.notImplemented() else -> result.notImplemented()
@ -95,7 +100,35 @@ class FlutterSmsPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
return !(activityInfo == null || !activityInfo.exported) return !(activityInfo == null || !activityInfo.exported)
} }
private fun sendSMS(result: Result, phones: String?, message: String?) { private fun sendSMS(result: Result, phones: String, message: String, sendDirect: Boolean) {
if (sendDirect) {
sendSMSDirect(result, phones, message);
}
else {
sendSMSDialog(result, phones, message);
}
}
private fun sendSMSDirect(result: Result, phones: String, message: String) {
// SmsManager is android.telephony
val sentIntent = PendingIntent.getBroadcast(activity, 0, Intent("SMS_SENT_ACTION"), PendingIntent.FLAG_IMMUTABLE)
val mSmsManager = SmsManager.getDefault()
val numbers = phones.split(";")
for (num in numbers) {
Log.d("Flutter SMS", "msg.length() : " + message.toByteArray().size)
if (message.toByteArray().size > 80) {
val partMessage = mSmsManager.divideMessage(message)
mSmsManager.sendMultipartTextMessage(num, null, partMessage, null, null)
} else {
mSmsManager.sendTextMessage(num, null, message, sentIntent, null)
}
}
result.success("SMS Sent!")
}
private fun sendSMSDialog(result: Result, phones: String, message: String) {
val intent = Intent(Intent.ACTION_SENDTO) val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("smsto:$phones") intent.data = Uri.parse("smsto:$phones")
intent.putExtra("sms_body", message) intent.putExtra("sms_body", message)

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_sms","path":"/Users/rodydavis/Developer/GitHub/plugins/packages/flutter_sms/","dependencies":["url_launcher"]},{"name":"url_launcher","path":"/usr/local/Caskroom/flutter/1.2.1/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.12/","dependencies":[]}],"android":[{"name":"flutter_sms","path":"/Users/rodydavis/Developer/GitHub/plugins/packages/flutter_sms/","dependencies":["url_launcher"]},{"name":"url_launcher","path":"/usr/local/Caskroom/flutter/1.2.1/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.12/","dependencies":[]}],"macos":[{"name":"url_launcher_macos","path":"/usr/local/Caskroom/flutter/1.2.1/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-2.0.2/","dependencies":[]}],"linux":[{"name":"url_launcher_linux","path":"/usr/local/Caskroom/flutter/1.2.1/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-2.0.2/","dependencies":[]}],"windows":[{"name":"url_launcher_windows","path":"/usr/local/Caskroom/flutter/1.2.1/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-2.0.2/","dependencies":[]}],"web":[{"name":"flutter_sms","path":"/Users/rodydavis/Developer/GitHub/plugins/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_web","path":"/usr/local/Caskroom/flutter/1.2.1/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-2.0.4/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_sms","dependencies":["url_launcher"]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-09-27 10:56:42.626795","version":"2.5.1"} {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_sms","path":"/Users/dsilk/Development/flutter/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_ios","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_ios-6.0.15/","dependencies":[]}],"android":[{"name":"flutter_sms","path":"/Users/dsilk/Development/flutter/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_android","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_android-6.0.15/","dependencies":[]}],"macos":[{"name":"url_launcher_macos","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-3.0.0/","dependencies":[]}],"linux":[{"name":"url_launcher_linux","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-3.0.0/","dependencies":[]}],"windows":[{"name":"url_launcher_windows","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-3.0.0/","dependencies":[]}],"web":[{"name":"flutter_sms","path":"/Users/dsilk/Development/flutter/packages/flutter_sms/","dependencies":[]},{"name":"url_launcher_web","path":"/Users/dsilk/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-2.0.8/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_sms","dependencies":["url_launcher"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2022-02-14 16:50:47.982162","version":"2.10.1"}

View File

@ -5,8 +5,11 @@
In most cases you can leave this as-is, but you if you want to provide In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<application <application
android:name="io.flutter.app.FlutterApplication" android:name="${applicationName}"
android:label="example" android:label="example"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
@ -15,6 +18,7 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:exported="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
jcenter() jcenter()

View File

@ -15,6 +15,7 @@ class _MyAppState extends State<MyApp> {
String? _message, body; String? _message, body;
String _canSendSMSMessage = 'Check is not run.'; String _canSendSMSMessage = 'Check is not run.';
List<String> people = []; List<String> people = [];
bool sendDirect = false;
@override @override
void initState() { void initState() {
@ -30,7 +31,10 @@ class _MyAppState extends State<MyApp> {
Future<void> _sendSMS(List<String> recipients) async { Future<void> _sendSMS(List<String> recipients) async {
try { try {
String _result = await sendSMS( String _result = await sendSMS(
message: _controllerMessage.text, recipients: recipients); message: _controllerMessage.text,
recipients: recipients,
sendDirect: sendDirect,
);
setState(() => _message = _result); setState(() => _message = _result);
} catch (error) { } catch (error) {
setState(() => _message = error.toString()); setState(() => _message = error.toString());
@ -89,7 +93,7 @@ class _MyAppState extends State<MyApp> {
), ),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
if (people == null || people.isEmpty) if (people.isEmpty)
const SizedBox(height: 0) const SizedBox(height: 0)
else else
SizedBox( SizedBox(
@ -144,12 +148,21 @@ class _MyAppState extends State<MyApp> {
}, },
), ),
), ),
SwitchListTile(
title: const Text('Send Direct'),
subtitle: const Text('Should we skip the additional dialog?'),
value: sendDirect,
onChanged: (bool newValue) {
setState(() {
sendDirect = newValue;
});
}),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: ElevatedButton( child: ElevatedButton(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith( backgroundColor: MaterialStateProperty.resolveWith(
(states) => Theme.of(context).accentColor), (states) => Theme.of(context).colorScheme.secondary),
padding: MaterialStateProperty.resolveWith( padding: MaterialStateProperty.resolveWith(
(states) => const EdgeInsets.symmetric(vertical: 16)), (states) => const EdgeInsets.symmetric(vertical: 16)),
), ),
@ -158,7 +171,7 @@ class _MyAppState extends State<MyApp> {
}, },
child: Text( child: Text(
'SEND', 'SEND',
style: Theme.of(context).accentTextTheme.button, style: Theme.of(context).textTheme.displayMedium,
), ),
), ),
), ),

View File

@ -1,16 +1,18 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'src/flutter_sms_platform.dart'; import 'src/flutter_sms_platform.dart';
/// Open SMS Dialog on iOS/Android/Web /// Open SMS Dialog on iOS/Android/Web
Future<String> sendSMS({ Future<String> sendSMS({
required String message, required String message,
required List<String> recipients, required List<String> recipients,
required bool sendDirect,
}) => }) =>
FlutterSmsPlatform.instance FlutterSmsPlatform.instance.sendSMS(
.sendSMS(message: message, recipients: recipients); message: message,
recipients: recipients,
sendDirect: sendDirect,
);
/// Launch SMS Url Scheme on all platforms /// Launch SMS Url Scheme on all platforms
Future<bool> launchSms({ Future<bool> launchSms({

View File

@ -15,6 +15,7 @@ class FlutterSmsPlugin extends FlutterSmsPlatform {
Future<String> sendSMS({ Future<String> sendSMS({
required String message, required String message,
required List<String> recipients, required List<String> recipients,
bool sendDirect = false,
}) async { }) async {
bool _messageSent = bool _messageSent =
await FlutterSmsPlatform.instance.launchSmsMulti(recipients, message); await FlutterSmsPlatform.instance.launchSmsMulti(recipients, message);

View File

@ -32,9 +32,12 @@ class FlutterSmsPlatform extends PlatformInterface {
_instance = instance; _instance = instance;
} }
///
///
Future<String> sendSMS({ Future<String> sendSMS({
required String message, required String message,
required List<String> recipients, required List<String> recipients,
required bool sendDirect,
}) { }) {
final mapData = <dynamic, dynamic>{}; final mapData = <dynamic, dynamic>{};
mapData['message'] = message; mapData['message'] = message;
@ -46,6 +49,7 @@ class FlutterSmsPlatform extends PlatformInterface {
} else { } else {
String _phones = recipients.join(';'); String _phones = recipients.join(';');
mapData['recipients'] = _phones; mapData['recipients'] = _phones;
mapData['sendDirect'] = sendDirect;
return _channel return _channel
.invokeMethod<String>('sendSMS', mapData) .invokeMethod<String>('sendSMS', mapData)
.then((value) => value ?? 'Error sending sms'); .then((value) => value ?? 'Error sending sms');