sms_sender_plus 0.2.0
sms_sender_plus: ^0.2.0 copied to clipboard
Send SMS from Flutter with Android SIM selection, delivery status, batch recipients, and iOS composer support.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sms_sender_plus/sms_sender_plus.dart';
void main() {
runApp(const SmsSenderPlusExampleApp());
}
class SmsSenderPlusExampleApp extends StatelessWidget {
const SmsSenderPlusExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'sms_sender_plus example',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.teal),
home: const SmsExampleScreen(),
);
}
}
class SmsExampleScreen extends StatefulWidget {
const SmsExampleScreen({super.key});
@override
State<SmsExampleScreen> createState() => _SmsExampleScreenState();
}
class _SmsExampleScreenState extends State<SmsExampleScreen> {
final _recipientController = TextEditingController();
final _batchController = TextEditingController();
final _messageController = TextEditingController(
text: 'Hello from sms_sender_plus',
);
final _events = <String>[];
List<SimCard> _simCards = const [];
int? _selectedSlot;
bool _deliveryReport = true;
bool _busy = false;
StreamSubscription<SmsStatusEvent>? _statusSubscription;
@override
void initState() {
super.initState();
_statusSubscription = SmsSenderPlus.instance.statusEvents.listen((event) {
_addEvent(
'${event.messageId}: ${event.status.name} ${event.recipient ?? ''}',
);
});
_loadSimCards();
}
@override
void dispose() {
_statusSubscription?.cancel();
_recipientController.dispose();
_batchController.dispose();
_messageController.dispose();
super.dispose();
}
Future<void> _loadSimCards() async {
final hasPhoneStatePermission = await _ensurePhoneStatePermission();
if (!hasPhoneStatePermission) {
_addEvent('READ_PHONE_STATE permission is required to load SIM cards.');
return;
}
await _run(() async {
final simCards = await SmsSenderPlus.instance.getSimCards();
setState(() => _simCards = simCards);
});
}
Future<bool> _ensureSmsPermission() async {
final hasPermission = await SmsSenderPlus.instance.checkSmsPermission();
if (hasPermission) {
return true;
}
return SmsSenderPlus.instance.requestSmsPermission();
}
Future<bool> _ensurePhoneStatePermission() async {
final hasPermission =
await SmsSenderPlus.instance.checkPhoneStatePermission();
if (hasPermission) {
return true;
}
return SmsSenderPlus.instance.requestPhoneStatePermission();
}
Future<void> _sendSingle() async {
final hasSmsPermission = await _ensureSmsPermission();
if (!hasSmsPermission) {
_addEvent('SEND_SMS permission is required to send a message.');
return;
}
if (_selectedSlot != null) {
final hasPhoneStatePermission = await _ensurePhoneStatePermission();
if (!hasPhoneStatePermission) {
_addEvent(
'READ_PHONE_STATE permission is required to select a SIM slot.',
);
return;
}
}
await _run(() async {
final result = await SmsSenderPlus.instance.sendTextMessage(
recipient: _recipientController.text,
message: _messageController.text,
simSlot: _selectedSlot,
deliveryReport: _deliveryReport,
);
_addEvent('single result: ${result.state.name} ${result.messageId}');
});
}
Future<void> _sendBatch() async {
final hasSmsPermission = await _ensureSmsPermission();
if (!hasSmsPermission) {
_addEvent('SEND_SMS permission is required to send messages.');
return;
}
if (_selectedSlot != null) {
final hasPhoneStatePermission = await _ensurePhoneStatePermission();
if (!hasPhoneStatePermission) {
_addEvent(
'READ_PHONE_STATE permission is required to select a SIM slot.',
);
return;
}
}
await _run(() async {
final result = await SmsSenderPlus.instance.sendTextMessages(
recipients: _batchController.text.split(','),
message: _messageController.text,
simSlot: _selectedSlot,
);
_addEvent('batch result: ${result.state.name} ${result.messageId}');
});
}
Future<void> _run(Future<void> Function() action) async {
setState(() => _busy = true);
try {
await action();
} on SmsSenderPlusException catch (error) {
_addEvent('${error.code}: ${error.message}');
} finally {
if (mounted) {
setState(() => _busy = false);
}
}
}
void _addEvent(String message) {
if (!mounted) {
return;
}
setState(() => _events.insert(0, message));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('sms_sender_plus')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
TextField(
controller: _recipientController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(labelText: 'Single recipient'),
),
const SizedBox(height: 12),
TextField(
controller: _batchController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Batch recipients, comma separated',
),
),
const SizedBox(height: 12),
TextField(
controller: _messageController,
minLines: 3,
maxLines: 5,
decoration: const InputDecoration(labelText: 'Message'),
),
const SizedBox(height: 12),
DropdownButtonFormField<int?>(
initialValue: _selectedSlot,
decoration: const InputDecoration(labelText: 'SIM slot'),
items: [
const DropdownMenuItem<int?>(
value: null,
child: Text('Device default'),
),
..._simCards.map(
(sim) => DropdownMenuItem<int?>(
value: sim.simSlotIndex,
child: Text(
'Slot ${sim.simSlotIndex}: '
'${sim.carrierName ?? sim.displayName ?? sim.subscriptionId}',
),
),
),
],
onChanged: (value) => setState(() => _selectedSlot = value),
),
SwitchListTile(
value: _deliveryReport,
title: const Text('Delivery report for single SMS'),
onChanged: (value) => setState(() => _deliveryReport = value),
),
const SizedBox(height: 12),
FilledButton(
onPressed: _busy ? null : _sendSingle,
child: const Text('Send single'),
),
OutlinedButton(
onPressed: _busy ? null : _sendBatch,
child: const Text('Send batch'),
),
const SizedBox(height: 24),
Text('Events', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
for (final event in _events) Text(event),
],
),
);
}
}