preferred_upi_launcher 0.0.2
preferred_upi_launcher: ^0.0.2 copied to clipboard
A Flutter plugin to discover installed UPI apps and launch them with pre-filled payment details. No payment gateway required.
import 'package:flutter/material.dart';
import 'package:preferred_upi_launcher/preferred_upi_launcher.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'UPI Launcher Demo', theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true), home: const UpiDemoPage());
}
}
class UpiDemoPage extends StatefulWidget {
const UpiDemoPage({super.key});
@override
State<UpiDemoPage> createState() => _UpiDemoPageState();
}
class _UpiDemoPageState extends State<UpiDemoPage> {
List<UpiAppInfo> _apps = [];
bool _loading = false;
String? _error;
// Replace payeeVpa and transactionRef with real values before live testing
static const _config = UpiPaymentConfig(payeeVpa: 'ashankartri****@oksbi', payeeName: 'Aditya', amount: '1', transactionRef: 'DEMO_TX_001', transactionNote: 'Demo payment');
@override
void initState() {
super.initState();
_fetchApps();
}
Future<void> _fetchApps() async {
setState(() {
_loading = true;
_error = null;
});
try {
final apps = await PreferredUpiLauncher.getInstalledApps();
setState(() => _apps = apps);
} catch (e) {
setState(() => _error = e.toString());
} finally {
setState(() => _loading = false);
}
}
Future<void> _launch(UpiAppInfo app) async {
try {
await PreferredUpiLauncher.launchApp(app: app, config: _config);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to launch ${app.name}: $e')));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('UPI Launcher Demo'), actions: [IconButton(icon: const Icon(Icons.refresh), tooltip: 'Refresh', onPressed: _fetchApps)]),
body: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [_PaymentInfoCard(config: _config), Expanded(child: _buildBody())]),
);
}
Widget _buildBody() {
if (_loading) return const Center(child: CircularProgressIndicator());
if (_error != null) {
return Center(child: Padding(padding: const EdgeInsets.all(24), child: Text('Error: $_error', style: const TextStyle(color: Colors.red))));
}
if (_apps.isEmpty) {
return const Center(child: Padding(padding: EdgeInsets.all(24), child: Text('No UPI apps found.\nTest on a physical device with UPI apps installed.', textAlign: TextAlign.center)));
}
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: _apps.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final app = _apps[index];
return ListTile(
leading: const CircleAvatar(child: Icon(Icons.account_balance_wallet_outlined)),
title: Text(app.name, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: Text(app.packageName, style: Theme.of(context).textTheme.bodySmall),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
onTap: () => _launch(app),
);
},
);
}
}
class _PaymentInfoCard extends StatelessWidget {
final UpiPaymentConfig config;
const _PaymentInfoCard({required this.config});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Payment Details', style: theme.textTheme.titleMedium),
const SizedBox(height: 8),
_row('Payee', '${config.payeeName} (${config.payeeVpa})'),
_row('Amount', '₹${config.amount}'),
_row('Ref', config.transactionRef),
],
),
),
);
}
Widget _row(String label, String value) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(children: [SizedBox(width: 56, child: Text('$label:', style: const TextStyle(color: Colors.grey))), Expanded(child: Text(value))]),
);
}