fundstrack_lite 0.0.11 copy "fundstrack_lite: ^0.0.11" to clipboard
fundstrack_lite: ^0.0.11 copied to clipboard

A Flutter plugin to extract and parse debit/credit bank SMS on Android. Supports UPI and bank notifications.

fundstrack_lite #

A Flutter plugin for parsing and extracting banking transaction details from SMS inbox on Android devices.

  • Detects and parses both traditional bank and UPI SMS notifications.
  • Extracts: Amount, Account Number, Date, Time, Transaction Type (debit/credit), and Sender.
  • ✅ Background-compatible with WorkManager support for silent syncing.

⚠️ Android only: iOS is not supported due to platform restrictions.


Features #

  • Reads Android SMS inbox for debit/credit transaction messages
  • Extracts structured data:
    • Amount
    • Account (last 4/6 digits)
    • Sender (e.g. HDFCBK, AXISBK, etc.)
    • Date, Time
    • Transaction Type: debit or credit
    • Original message body
  • Built-in background support via WorkManager
  • Designed for integration with Flutter UI and APIs

Installation #

Add to your pubspec.yaml:

dependencies:
  fundstrack_lite: ^0.0.8
  permission_handler: ^11.3.0

Android Setup #

  1. Add Permissions in AndroidManifest.xml Place the following outside the
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

2. Request Permissions at Runtime #

import 'package:permission_handler/permission_handler.dart';

Future<void> requestSmsPermission() async {
  await Permission.sms.request();
  await Permission.phone.request();
}

Background Sync via WorkManager #

You can also use the direct getter:

import 'package:fundstrack_lite/fundstrack_lite.dart';

final smsList = await FundstrackLite.getBankSms([]); // returns List<Map<String, dynamic>>

This will enqueue a WorkManager job on the native side (Android only) to read SMS and push to your backend.

Parsing SMS Directly (Foreground) #

You can also use the direct getter:

import 'package:fundstrack_lite/fundstrack_lite.dart';

final smsList = await FundstrackLite.getBankSms([]); // returns List<Map<String, dynamic>>

Each entry includes:

Key Description
amount Parsed transaction amount
account Account number (masked/partial)
type "debit" or "credit"
sender Bank or UPI sender ID
date Parsed transaction date
time Parsed transaction time
smsTimestamp SMS received time (epoch)
body Full SMS text

Android Plugin Implementation (Kotlin) #

android/src/main/kotlin/com/fundsroom/fundstracklib/FundsTrackLitePlugin.kt
package com.fundsroom.fundstracklib

import android.content.Context
import android.content.Intent
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result

class FundsTrackLitePlugin : FlutterPlugin, MethodCallHandler {

    private lateinit var context: Context

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        context = binding.applicationContext
        val channel = MethodChannel(binding.binaryMessenger, "fundstrack_lite")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        when (call.method) {
            "ProcessFundsTrack" -> {
                val userId = call.argument<String>("userId") ?: ""
                SmsBankFetcher.userId = userId

                // ✅ Launch background processor (WorkManager internally or fallback)
                val intent = Intent(context, SmsProcessingService::class.java)
                intent.putExtra("userId", userId)
                context.startForegroundService(intent)

                result.success("FundsTrack started for user $userId")
            }

            else -> {
                result.notImplemented()
            }
        }
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        // No cleanup needed
    }
}

Example Flutter App (main.dart) #

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

void main() {
  runApp(const FundstrackLiteDemoApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'fundstrack_lite Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const BankSmsScreen(),
    );
  }
}

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

  @override
  State<BankSmsScreen> createState() => _BankSmsScreenState();
}

class _BankSmsScreenState extends State<BankSmsScreen> {
  List<Map<String, dynamic>>? _smsList;
  String? _error;
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _loadSms();
  }

  Future<void> _loadSms() async {
    setState(() {
      _loading = true;
      _error = null;
    });
    try {
      final sms = await Permission.sms.request();
      final phone = await Permission.phone.request();

      if (!sms.isGranted || !phone.isGranted) {
        setState(() {
          _error = 'SMS & phone permissions required.';
          _loading = false;
        });
        return;
      }

      final smsList = await FundstrackLite.getBankSms([]);
      setState(() {
        _smsList = smsList;
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
        _loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    Widget body;
    if (_loading) {
      body = const Center(child: CircularProgressIndicator());
    } else if (_error != null) {
      body = Center(child: Text('Error: $_error'));
    } else if (_smsList == null || _smsList!.isEmpty) {
      body = const Center(child: Text('No debit/credit SMS found.'));
    } else {
      body = _smsListView(_smsList!);
    }
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bank SMS Transactions'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _loadSms,
            tooltip: "Refresh",
          ),
        ],
      ),
      body: body,
    );
  }

  Widget _smsListView(List<Map<String, dynamic>> smsList) {
    return ListView.separated(
      itemCount: smsList.length,
      separatorBuilder: (_, __) => const Divider(),
      itemBuilder: (context, index) {
        final sms = smsList[index];
        final DateTime smsTime = DateTime.fromMillisecondsSinceEpoch(
          int.tryParse(sms['smsTimestamp']?.toString() ?? '0') ?? 0,
        );
        final dateStr = sms['date']?.isNotEmpty == true
            ? sms['date']
            : "${smsTime.day.toString().padLeft(2, '0')}/${smsTime.month.toString().padLeft(2, '0')}/${smsTime.year}";
        final timeStr = sms['time']?.isNotEmpty == true
            ? sms['time']
            : "${smsTime.hour.toString().padLeft(2, '0')}:${smsTime.minute.toString().padLeft(2, '0')}";

        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
          elevation: 2,
          child: Padding(
            padding: const EdgeInsets.all(14.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text("Amount: ${sms['amount'] ?? '--'}",
                    style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                Text("Account Number: ${sms['account'] ?? '--'}"),
                Text("Sender: ${sms['sender'] ?? '--'}"),
                Text("Type: ${sms['type'] ?? '--'}",
                    style: TextStyle(
                      color: (sms['type'] == 'debit')
                          ? Colors.red
                          : (sms['type'] == 'credit')
                              ? Colors.green
                              : Colors.black,
                      fontWeight: FontWeight.bold,
                    )),
                Text("Date: $dateStr"),
                Text("Time: $timeStr"),
                // Text("Body: ${sms['body']}"),
              ],
            ),
          ),
        );
      },
    );
  }
}

API Integration #

You can push SMS data to a backend API:

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> sendSmsToServer(Map<String, dynamic> sms) async {
  await http.post(
    Uri.parse('https://your-api-endpoint.com/sms'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode(sms),
  );
}

NDK Compatibility Fix (Optional) #

If you see this error:

NDK version mismatch
plugin fundstrack_lite requires NDK 27.0.12077973

Add this to your android/app/build.gradle.kts:

android {
    ndkVersion = "27.0.12077973"
}

Limitations #

  • ❌ iOS not supported (OS does not allow SMS inbox access)
  • Works only if READ_SMS permission is granted
  • Parsing may fail for non-standard bank formats
  • WorkManager background jobs are Android-only

Notes #

  • The plugin registers itself automatically via onAttachedToEngine() using Flutter v2 embedding.
  • You must declare this in your pubspec.yaml:
plugin:
  platforms:
    android:
      package: com.fundsroom.fundstracklib
      pluginClass: FundsTrackLitePlugin

Contributions #

We welcome contributions — help improve regex accuracy for different bank formats or submit issues/PRs for real-world data enhancements.

GitHub: https://github.com/fundsroom/fundstrack_lite

0
likes
0
points
21
downloads

Publisher

verified publisherfundsroom.com

Weekly Downloads

A Flutter plugin to extract and parse debit/credit bank SMS on Android. Supports UPI and bank notifications.

Homepage
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on fundstrack_lite

Packages that implement fundstrack_lite