thl_ussd_service 0.3.0 copy "thl_ussd_service: ^0.3.0" to clipboard
thl_ussd_service: ^0.3.0 copied to clipboard

PlatformAndroid

A Flutter plugin to make silent USSD requests and read their responses, using Android's sendUssdRequest. iOS is not supported. Upgraded fork of ussd_service.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:thl_ussd_service/thl_ussd_service.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'THL USSD Service Demo',
      theme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: const Color(0xFF1976D2),
        colorScheme: const ColorScheme.dark(
          primary: Color(0xFF1976D2),
          secondary: Color(0xFF03DAC6),
        ),
      ),
      home: const UssdDemoPage(),
    );
  }
}

class UssdDemoPage extends StatefulWidget {
  const UssdDemoPage({super.key});

  @override
  _UssdDemoPageState createState() => _UssdDemoPageState();
}

enum UssdMode {
  silent,
  interactiveSingle,
  interactiveMulti,
}

class _UssdDemoPageState extends State<UssdDemoPage> {
  UssdMode _ussdMode = UssdMode.silent;
  bool _isAccessibilityEnabled = false;
  bool _isOverlayPermissionGranted = false;
  List<Map<String, dynamic>> _simCards = [];

  int _selectedSimSubscriptionId = 0;
  int _selectedSimSlotIndex = 0;

  final TextEditingController _codeController =
      TextEditingController(text: '*123#');
  final TextEditingController _optionsController =
      TextEditingController(text: '1, 2, 3');
  final TextEditingController _overlayMessageController =
      TextEditingController(text: 'USSD session running...');

  bool _loading = false;
  String _statusMessage = 'Ready';
  final List<String> _messageLogs = [];

  @override
  void initState() {
    super.initState();
    _checkStatusAndSims();
    UssdService.setUssdMessageListener((message) {
      setState(() {
        _messageLogs.add('Received: $message');
      });
    });
  }

  @override
  void dispose() {
    UssdService.removeUssdMessageListener();
    _codeController.dispose();
    _optionsController.dispose();
    _overlayMessageController.dispose();
    super.dispose();
  }

  Future<void> _checkStatusAndSims() async {
    final access = await UssdService.isAccessibilityEnabled();
    final overlay = await UssdService.isOverlayPermissionGranted();

    await Permission.phone.request();
    final sims = await UssdService.getSimCards();

    setState(() {
      _isAccessibilityEnabled = access;
      _isOverlayPermissionGranted = overlay;
      _simCards = sims;
      if (_simCards.isNotEmpty) {
        _selectedSimSubscriptionId = _simCards.first['subscriptionId'] ?? 0;
        _selectedSimSlotIndex = _simCards.first['slotIndex'] ?? 0;
      }
    });
  }

  Future<void> _executeUssd() async {
    setState(() {
      _loading = true;
      _messageLogs.clear();
      _statusMessage = 'Initiating request...';
    });

    final code = _codeController.text.trim();

    try {
      if (_ussdMode == UssdMode.silent) {
        _statusMessage = 'Running silent request...';
        final response =
            await UssdService.makeRequest(_selectedSimSubscriptionId, code);
        setState(() {
          _statusMessage = 'Success';
          _messageLogs.add('Response: $response');
        });
      } else if (_ussdMode == UssdMode.interactiveSingle) {
        if (!_isAccessibilityEnabled) {
          throw Exception("Accessibility Service must be enabled in settings.");
        }
        _statusMessage = 'Running interactive single request...';
        final response = await UssdService.sendUssdRequest(
          ussdCode: code,
          subscriptionId: _selectedSimSubscriptionId,
        );
        setState(() {
          _statusMessage = 'Success';
          _messageLogs.add('Result: $response');
        });
      } else {
        if (!_isAccessibilityEnabled) {
          throw Exception("Accessibility Service must be enabled in settings.");
        }
        _statusMessage = 'Running multi-session automation...';

        final optionsString = _optionsController.text.trim();
        final options = optionsString.isNotEmpty
            ? optionsString.split(',').map((e) => e.trim()).toList()
            : <String>[];

        await UssdService.multisessionUssd(
          code: code,
          slotIndex: _selectedSimSlotIndex,
          options: options,
          overlayMessage: _overlayMessageController.text.trim(),
        );

        setState(() {
          _statusMessage = 'Automation finished / Completed';
        });
      }
    } on PlatformException catch (e) {
      setState(() {
        _statusMessage = 'Failed with platform error';
        _messageLogs.add('Error [${e.code}]: ${e.message}');
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Failed with error';
        _messageLogs.add('Error: ${e.toString()}');
      });
    } finally {
      setState(() {
        _loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('THL USSD Service'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _checkStatusAndSims,
          )
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status Card
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Permissions & Services',
                        style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                            color: Colors.blue)),
                    const SizedBox(height: 12),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text('Accessibility Service:'),
                        Row(
                          children: [
                            Icon(
                              _isAccessibilityEnabled
                                  ? Icons.check_circle
                                  : Icons.error,
                              color: _isAccessibilityEnabled
                                  ? Colors.green
                                  : Colors.red,
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: () async {
                                await UssdService.openAccessibilitySettings();
                              },
                              child: const Text('Settings'),
                            ),
                          ],
                        )
                      ],
                    ),
                    const SizedBox(height: 12),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text('Overlay Permission:'),
                        Row(
                          children: [
                            Icon(
                              _isOverlayPermissionGranted
                                  ? Icons.check_circle
                                  : Icons.warning,
                              color: _isOverlayPermissionGranted
                                  ? Colors.green
                                  : Colors.orange,
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: () async {
                                await UssdService.openOverlaySettings();
                              },
                              child: const Text('Settings'),
                            ),
                          ],
                        )
                      ],
                    )
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // SIM selector
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('SIM Card Selection',
                        style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                            color: Colors.blue)),
                    const SizedBox(height: 12),
                    if (_simCards.isEmpty)
                      const Text(
                          'No SIM cards detected or permissions missing. Click refresh at the top.')
                    else
                      DropdownButtonFormField<int>(
                        initialValue: _selectedSimSubscriptionId,
                        decoration:
                            const InputDecoration(labelText: 'Select SIM'),
                        items: _simCards.map((sim) {
                          return DropdownMenuItem<int>(
                            value: sim['subscriptionId'],
                            child: Text(
                                '[Slot ${sim['slotIndex']}] ${sim['displayName']} (${sim['carrierName']})'),
                          );
                        }).toList(),
                        onChanged: (val) {
                          if (val != null) {
                            final selected = _simCards.firstWhere(
                                (element) => element['subscriptionId'] == val);
                            setState(() {
                              _selectedSimSubscriptionId = val;
                              _selectedSimSlotIndex =
                                  selected['slotIndex'] ?? 0;
                            });
                          }
                        },
                      )
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Mode Selector
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Execution Mode',
                        style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                            color: Colors.blue)),
                    const SizedBox(height: 12),
                    RadioListTile<UssdMode>(
                      title: const Text('Silent Request (TelephonyManager)'),
                      subtitle: const Text(
                          'Completely silent. Android 8+. Only standard *...# codes.'),
                      value: UssdMode.silent,
                      groupValue: _ussdMode,
                      onChanged: (val) => setState(() => _ussdMode = val!),
                    ),
                    RadioListTile<UssdMode>(
                      title: const Text(
                          'Interactive Single Request (Accessibility)'),
                      subtitle:
                          const Text('Launches Dialer. Works with #101#451#.'),
                      value: UssdMode.interactiveSingle,
                      groupValue: _ussdMode,
                      onChanged: (val) => setState(() => _ussdMode = val!),
                    ),
                    RadioListTile<UssdMode>(
                      title: const Text(
                          'Interactive Multi-step Request (Accessibility)'),
                      subtitle: const Text(
                          'Sends multiple menu options sequentially.'),
                      value: UssdMode.interactiveMulti,
                      groupValue: _ussdMode,
                      onChanged: (val) => setState(() => _ussdMode = val!),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // USSD parameters input
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Parameters',
                        style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                            color: Colors.blue)),
                    const SizedBox(height: 12),
                    TextField(
                      controller: _codeController,
                      decoration: const InputDecoration(
                        labelText: 'USSD Code',
                        hintText: 'e.g. *123# or #101#451#',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    if (_ussdMode == UssdMode.interactiveMulti) ...[
                      const SizedBox(height: 12),
                      TextField(
                        controller: _optionsController,
                        decoration: const InputDecoration(
                          labelText: 'Menu Options (comma separated)',
                          hintText: 'e.g. 1, 2, 3',
                          border: OutlineInputBorder(),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _overlayMessageController,
                        decoration: const InputDecoration(
                          labelText: 'Overlay Message',
                          border: OutlineInputBorder(),
                        ),
                      ),
                    ]
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),

            // Execution Button
            ElevatedButton(
              onPressed: _loading ? null : _executeUssd,
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(vertical: 16),
                backgroundColor: const Color(0xFF1976D2),
                foregroundColor: Colors.white,
              ),
              child: _loading
                  ? const SizedBox(
                      width: 24,
                      height: 24,
                      child: CircularProgressIndicator(
                          color: Colors.white, strokeWidth: 2),
                    )
                  : const Text('Execute USSD',
                      style:
                          TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
            ),
            const SizedBox(height: 24),

            // Outputs and Logs
            Card(
              color: Colors.black45,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text('Status Logs',
                            style: TextStyle(
                                fontWeight: FontWeight.bold,
                                color: Colors.grey)),
                        Text(_statusMessage,
                            style: const TextStyle(
                                fontWeight: FontWeight.bold,
                                color: Colors.blue)),
                      ],
                    ),
                    const Divider(height: 24, color: Colors.grey),
                    if (_messageLogs.isEmpty)
                      const Text(
                          'No activity yet. Run a code to see responses here.',
                          style: TextStyle(
                              fontStyle: FontStyle.italic, color: Colors.grey))
                    else
                      ..._messageLogs.map((log) => Padding(
                            padding: const EdgeInsets.only(bottom: 8.0),
                            child: Text(log,
                                style:
                                    const TextStyle(fontFamily: 'monospace')),
                          )),
                  ],
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}
1
likes
140
points
250
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin to make silent USSD requests and read their responses, using Android's sendUssdRequest. iOS is not supported. Upgraded fork of ussd_service.

Repository (GitHub)
View/report issues

License

BSD-2-Clause (license)

Dependencies

flutter

More

Packages that depend on thl_ussd_service

Packages that implement thl_ussd_service