ussd_advanced_flutter

A Flutter plugin that allows you to send and read USSD codes programmatically on Android β€” with support for dual-SIM devices, real-time streaming, and timeout handling.

πŸͺ„ Works through a custom Android Accessibility Service β€” no root required.


πŸš€ Features

βœ… Send USSD codes directly from Flutter
βœ… Receive the final USSD response as a Future
βœ… Listen to all intermediate USSD messages in real time
βœ… Choose which SIM slot to use (dual-SIM supported)
βœ… Built-in timeout, no-SIM, and weak-signal handling
βœ… Works on Android 6.0+ with Kotlin backend


πŸ“¦ Installation

Add this to your pubspec.yaml:

dependencies:
  ussd_advanced_flutter: ^1.0.0

Then run:

flutter pub get

βš™οΈ Android Setup

1️⃣ Add required permissions

In your app’s AndroidManifest.xml (under android/app/src/main):

<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
    tools:ignore="ProtectedPermissions" />
    ```
    #### 2️⃣ Register the Accessibility Service
Inside the `<application>` tag:

```xml
<service
    android:name="com.example.ussd_advanced_flutter.UssdAccessibilityService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/ussd_accessibility_service_config" />
</service>

Ensure the file xml/ussd_accessibility_service_config.xml exists in:

android/app/src/main/res/xml/

🧩 Example Usage

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:ussd_advanced_flutter/ussd_advanced_flutter.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String? _lastResponse;

  @override
  void initState() {
    super.initState();
    UssdAdvancedFlutter.ussdStream.listen((event) {
      debugPrint('USSD Stream: $event');
    });
  }

  Future<void> _ensurePermissions() async {
    final status = await Permission.phone.request();
    if (!status.isGranted) openAppSettings();
  }

  Future<void> _sendUssd() async {
    await _ensurePermissions();
    try {
      final response = await UssdAdvancedFlutter.sendUssdForResponse(
        '*100#',
        simSlot: 0,
        timeout: const Duration(seconds: 10),
      );
      setState(() => _lastResponse = response);
    } catch (e) {
      debugPrint('USSD Error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('USSD Advanced Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: UssdAdvancedFlutter.openAccessibilitySettings,
                child: const Text('Enable Accessibility Service'),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _sendUssd,
                child: const Text('Send *100#'),
              ),
              const SizedBox(height: 16),
              Text('Response:\n${_lastResponse ?? "β€”"}'),
            ],
          ),
        ),
      ),
    );
  }
}

πŸ”„ API Reference

Method Description
sendUssd(String code, {int simSlot = 0}) Sends a USSD code (fire-and-forget).
sendUssdForResponse(String code, {int simSlot = 0, Duration timeout}) Sends a USSD code and waits for the final response.
ussdStream Broadcast stream of all live USSD responses.
openAccessibilitySettings() Opens Android settings to enable the plugin’s service.

⚠️ Limitations

  • Android only β€” iOS doesn’t allow USSD automation.
  • User must manually enable the Accessibility Service for this plugin.
  • Some OEMs (Xiaomi, Huawei, Oppo) may require keeping the app in the background whitelist.
  • USSD session duration depends on the carrier β€” recommended timeout: 8–12 seconds.

🧰 Troubleshooting

Problem Solution
No USSD responses received. Increase timeout or check signal strength.
PlatformException(PERMISSION) Grant CALL_PHONE and READ_PHONE_STATE permissions.
No MaterialLocalizations found. Wrap your app with MaterialApp.
No SIM card detected. Insert and activate a SIM before running.
Weak or dropped signal. Move to an area with better reception and retry.

πŸ“š Example (short version for pub.flutter-io.cn)

final resp = await UssdAdvancedFlutter.sendUssdForResponse(
  "*100#",
  simSlot: 0,
  timeout: const Duration(seconds: 10),
);

print(resp); // Example: "Your balance is 989.49 SYP"

πŸ§‘β€πŸ’» Author

Numa Alset πŸ”— GitHub: numaalset πŸ“§ numaalset@gmail.com


πŸͺͺ License

MIT License β€” see LICENSE for details.