sms_insights_flutter 0.0.1-dev.2
sms_insights_flutter: ^0.0.1-dev.2 copied to clipboard
Flutter plugin wrapping the PayU SMS Insights Android SDK (core + optional logger). Reads transactional SMS for credit assessment with consent-based permission flow.
sms_insights_flutter #
Flutter plugin that wraps the PayU SMS Insights Android SDKs. Reads transactional SMS for credit-assessment with a consent-based permission flow, and exposes optional logging / analytics hooks.
Android only (
minSdk 21). Calling the plugin on iOS raisesMissingPluginException.
What it wraps #
| Native artifact | Maven coordinate | Required? |
|---|---|---|
| Core SDK | in.payuinnovations:payuinnovations-smsinsights-core-sdk:1.0.1-20260715.071507-5 |
Yes (declared by the plugin) |
| Network SDK | in.payuinnovations:payuinnovations-smsinsights-network-sdk:1.0.1-20260714.114535-8 |
No — transitive from core-sdk (see note below) |
| Logger SDK | in.payuinnovations:payuinnovations-smsinsights-logger-sdk:1.0.1-20260714.104324-5 |
No (you add it in your app's build.gradle if you want logging) |
Network SDK is transitive. The core SDK POM declares
payuinnovations-smsinsights-network-sdkas a runtime dependency, and Gradle resolves it automatically — consumers do not need to add it separately. The plugin pins an aligned network-sdk build explicitly (seeandroid/sdk_versions.gradle) so Gradle does not pick up a stale SNAPSHOT cache entry with the oldcom.smsinsights.network_library.*package.
The plugin exposes two customer-facing Dart classes:
SmsSdkWrapper— full lifecycle for the SMS Insights core SDK (init,startSync,setDeviceMatch,forgetUser) plus aStream<SdkEvent>for callbacks.LoggerWrapper—setLog,setIdentity,logEvent. ThrowsLoggerNotInstalledExceptionif the optional logger artifact is missing from the consumer app.
The plugin automatically tags every init call with platform: flutter
and the plugin semver (wrapperVersion) for backend telemetry — you do
not need to configure these.
Getting Started #
Step 1: Add the plugin to your app #
In your Flutter app's pubspec.yaml:
dependencies:
sms_insights_flutter: ^0.1.0
Then:
flutter pub get
If you depend on a -SNAPSHOT core SDK version, ensure your
project-level build.gradle includes the Sonatype snapshot repository:
repositories {
mavenCentral()
google()
maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
}
Step 2: Permissions #
All seven required permissions are declared in the plugin's own
AndroidManifest.xml and are automatically merged into your app
via Android's manifest merger — you do not need to add anything to
your app's manifest.
| Permission | Type |
|---|---|
INTERNET |
normal |
READ_SMS |
dangerous |
ACCESS_COARSE_LOCATION |
dangerous |
ACCESS_FINE_LOCATION |
dangerous |
READ_PHONE_STATE |
dangerous |
READ_PHONE_NUMBERS |
dangerous |
QUERY_ALL_PACKAGES |
dangerous |
Use a runtime-permission package (e.g. permission_handler) to
prompt the user for dangerous permissions — the SDK asks for them
via the RequestPermissionsEvent flow described in Step 5.
Step 3 (optional): Enable logging #
If you want OpenTelemetry traces and CleverTap analytics, add the
optional logger artifact to your app's android/app/build.gradle:
dependencies {
implementation "in.payuinnovations:payuinnovations-smsinsights-logger-sdk:1.0.1-20260714.104324-5"
}
Skip this step if you don't need logging — LoggerWrapper will simply
throw LoggerNotInstalledException and SmsSdkWrapper will continue to
work normally.
Step 4: Initialise the SDK #
Subscribe to the events stream before calling init:
import 'package:sms_insights_flutter/sms_insights_flutter.dart';
SmsSdkWrapper.instance.events.listen((event) {
switch (event) {
case InitSuccessEvent():
print('SDK ready');
case InitFailureEvent(:final code, :final message):
print('Init failed: $code $message');
default:
break;
}
});
await SmsSdkWrapper.instance.init(
const SdkClientConfig(
clientEmail: 'you@yourcompany.com',
accessId: 'your-access-key',
customerId: 'end-user-id',
// optional fields:
upperLimit: 180,
incomePrediction: IncomeEstimation(salary: 50000, company: 'Acme Corp'),
environment: SdkEnvironmentKind.staging, // default is staging
),
);
For production:
environment: SdkEnvironmentKind.production,
For a custom backend:
environment: SdkEnvironmentKind.custom,
customEnvironment: CustomEnvironmentConfig(
testEmail: 'test@example.com',
accessKey: 'key',
envUrl: 'https://your-base-url/',
signozUrl: 'https://your-signoz-url/',
),
init returns once the native call returns. Auth result arrives asynchronously
on the events stream subscribed above.
Step 5: Start sync (with permission handshake) #
Subscribe to events before calling startSync so
RequestPermissionsEvent is not missed:
import 'package:permission_handler/permission_handler.dart';
SmsSdkWrapper.instance.events.listen((event) async {
if (event is RequestPermissionsEvent) {
final granted = await _requestPermissions(event.permissions);
await SmsSdkWrapper.instance.grantPermissions(
requestId: event.requestId,
grantedPermissions: granted,
);
}
});
await SmsSdkWrapper.instance.startSync();
// Maps the SDK's Android permission strings to permission_handler objects,
// then requests them all in a single system dialog (same as Kotlin's
// requestPermissionsLauncher.launch(permissions.toTypedArray())).
Future<List<String>> _requestPermissions(List<String> requested) async {
// Build Permission → [android name, ...] map.
// ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION both map to
// Permission.location; when location is granted both strings are returned.
final mapping = <Permission, List<String>>{};
for (final p in requested) {
final handler = switch (p) {
'android.permission.READ_SMS' => Permission.sms,
'android.permission.ACCESS_FINE_LOCATION' => Permission.location,
'android.permission.ACCESS_COARSE_LOCATION' => Permission.location,
'android.permission.READ_PHONE_STATE' => Permission.phone,
'android.permission.READ_PHONE_NUMBERS' => Permission.phone,
_ => null,
};
if (handler != null) (mapping[handler] ??= []).add(p);
}
if (mapping.isEmpty) return [];
final statuses = await mapping.keys.toList().request();
return [
for (final entry in statuses.entries)
if (entry.value.isGranted) ...?mapping[entry.key],
];
}
Sync results (SmsSyncSuccessEvent, NoMessagesToSyncEvent, etc.) also
arrive on the same events stream.
Running the bundled example #
cd example
cp lib/client_credentials.example.dart lib/client_credentials.dart
# Edit lib/client_credentials.dart with your staging credentials
flutter pub get
flutter run
The example pre-fills credentials from client_credentials.dart (gitignored).
client_credentials.example.dart ships placeholder values — copy it before
your first run.
Subscribe to SmsSdkWrapper.instance.events before calling init so
InitSuccessEvent / InitFailureEvent are not dropped.
Step 6: Optional logging #
final available = await LoggerWrapper.instance.isAvailable;
if (available) {
await LoggerWrapper.instance.setIdentity(
customerId: 'end-user-id',
clientEmail: 'you@yourcompany.com',
);
await LoggerWrapper.instance.setLog('SDK initialised');
await LoggerWrapper.instance.logEvent(
'SDK_INIT_SUCCESS',
data: {'source': 'flutter'},
);
}
Step 7: Forget user / cleanup #
await SmsSdkWrapper.instance.forgetUser();
Cancels all background sync workers and purges persisted state. The native
wrapper releases its SDK instance so you can call init again. There is no
success callback — failures arrive as ForgetUserFailureEvent on the events
stream.
API Reference #
SmsSdkWrapper #
Future<void> init(SdkClientConfig config)Future<void> startSync()Future<bool> grantPermissions({required String requestId, required List<String> grantedPermissions})Future<void> setDeviceMatch(DeviceMatch match)Future<void> forgetUser()Stream<SdkEvent> get events
LoggerWrapper #
Future<bool> get isAvailableFuture<void> setLog(String message, {List<Object?> args = const []})Future<void> setIdentity({String? customerId, String? androidId, String? clientEmail})Future<void> logEvent(String eventName, {Map<String, Object> data = const {}})
Models #
SdkClientConfig({required clientEmail, required accessId, required customerId, upperLimit?, incomePrediction?, environment?, customEnvironment?})IncomeEstimation({required salary, required company})CustomEnvironmentConfig({required testEmail, required accessKey, required envUrl, required signozUrl})SdkEnvironmentKind—staging,production,customDeviceMatch({email?, name?, phoneNo?})
Events (sealed SdkEvent) #
InitSuccessEvent, InitFailureEvent, RequestPermissionsEvent,
SmsSyncSuccessEvent, NoMessagesToSyncEvent, UploadFailureEvent,
ForgetUserFailureEvent, MatchDeviceFailureEvent,
SmsPermissionNotAvailableEvent, InitNotCalledEvent, UnknownSdkEvent.
Error handling #
All synchronous failures surface as SmsInsightsException:
INVALID_ARGUMENT— a required field was empty or the wrong type.NATIVE_ERROR— the native SDK threw; checkdetailsfor the Kotlin stack trace.loggerNotInstalled— the consumer app did not add the optional logger Maven artifact (raised asLoggerNotInstalledException, which is a subclass ofSmsInsightsException).
Asynchronous failures from the native SDK arrive on the events
stream — handle InitFailureEvent, UploadFailureEvent,
ForgetUserFailureEvent, etc.
ProGuard / R8 #
The core SDK ships consumer ProGuard rules. If minification still
strips SDK classes, add to your proguard-rules.pro:
-keep class in.payuinnovations.insights.** { *; }
-keep class in.payuinnovations.network_library.** { *; }
-keep class com.smsinsights.network_library.** { *; }
-keep class com.google.gson.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
Keep both in.payuinnovations.network_library.** and
com.smsinsights.network_library.** until PayU fully migrates all published
network-sdk builds to the renamed package.
Permissions reference #
android.permission.INTERNETandroid.permission.READ_SMSandroid.permission.ACCESS_COARSE_LOCATIONandroid.permission.ACCESS_FINE_LOCATIONandroid.permission.READ_PHONE_STATEandroid.permission.READ_PHONE_NUMBERSandroid.permission.QUERY_ALL_PACKAGES
Troubleshooting #
NATIVE_ERROR/ClassNotFoundException: RegexInfoon init — the core-sdk and network-sdk SNAPSHOT builds on your machine are out of sync. Core SDK1.0.1-SNAPSHOT(Jul 2026+) expectsin.payuinnovations.network_library.models.RegexInfo, but older network-sdk SNAPSHOT caches still shipcom.smsinsights.network_library.models.RegexInfo. Fix options:- Use the aligned snapshot builds pinned in
android/sdk_versions.gradle(recommended — the plugin already does this). - Refresh dependencies:
./gradlew --refresh-dependenciesthenflutter clean && flutter run. - Inspect the resolved AAR:
jar tf classes.jar | grep RegexInfo— should showin/payuinnovations/network_library/models/RegexInfo.class. - If no aligned build is available, contact PayU Mobile — this is a native SDK publishing mismatch, not fixable in Dart/Kotlin wrapper code.
- Use the aligned snapshot builds pinned in
LoggerNotInstalledException— addimplementation "in.payuinnovations:payuinnovations-smsinsights-logger-sdk:1.0.1-20260714.104324-5"to your app'sandroid/app/build.gradleand rebuild. Or callLoggerWrapper.instance.isAvailablefirst to skip logger calls gracefully.InitNotCalledEventafterstartSync— callinitand wait forInitSuccessEventbefore invokingstartSync.SmsPermissionNotAvailableEvent— the user denied SMS permission; guide the user to grantREAD_SMSin app settings.- Maven Central snapshot 403 — add the Sonatype snapshot repository (see Step 1).
Device QA checklist (before production release) #
Run on a physical Android device with the bundled example app or your integration:
- Subscribe to
events, then callinit— confirmInitSuccessEvent. - Call
startSync— grant all requested permissions when prompted. - Confirm sync result (
SmsSyncSuccessEventorNoMessagesToSyncEvent). - Call
forgetUser, theninitagain — confirm a second session works. - (Optional) Call
setDeviceMatchbeforestartSyncand verify noMatchDeviceFailureEvent. - (Optional) With logger SDK added, confirm
LoggerWrappercalls succeed.
Native SDK dependencies are pinned to aligned SNAPSHOT builds in
android/sdk_versions.gradle. Prefer stable 1.0.1+ artifacts from PayU
when available before a production release.
Releasing #
pubspec.yaml is the single source of truth for the plugin version. It
drives pub.flutter-io.cn publishing, the wrapperVersion sent to the native SDK on
every init, and the Android library artifact version (read from pubspec
in android/build.gradle).
Release checklist:
# 1. Bump version in pubspec.yaml (e.g. 0.1.0 → 0.1.1)
# 2. Sync the generated Dart constant used for wrapperVersion telemetry
dart run tool/sync_version.dart
flutter test
# 3. Update CHANGELOG.md
# 4. Publish
flutter pub publish
lib/src/version.dart is generated — do not edit it by hand. The drift
test in test/version_sync_test.dart fails if it falls out of sync with
pubspec.
License #
Apache-2.0. See LICENSE.