Flutter integration guide
How to embed the Aarambh SDK in a Flutter app via flutter-plugin/
(aarambh_sdk). The package is a thin pass-through — every Dart method forwards to
com.aarambh.sdk.AarambhSDK over a single MethodChannel. No business logic lives in
the Flutter or Kotlin layers.
Validated end-to-end. The full call chain — from a Dart method call, through the plugin bridge, into the SDK's native layer, and on to either its own UI or a direct API call — has been built, installed, and run against a real device, including a live headless login call round-tripping to the backend.
Platform support
Android only. flutter-plugin/pubspec.yaml declares a single platform:
flutter:
plugin:
platforms:
android:
package: com.aarambh.sdk.flutter
pluginClass: AarambhSdkPlugin
There is no ios/ directory and no iOS SDK to bind against yet. A Flutter app
that also ships iOS will need its own conditional handling (e.g. checking Platform.isAndroid)
until an iOS binding exists.
Architecture
Every call takes the same route from Dart down to native, whether it opens the SDK's own UI or just fires a REST call.
-
Flutter app (Dart)
-
AarambhSdk
lib/aarambh_sdk.dart -
MethodChannel('aarambh_sdk')
-
AarambhSdkPlugin.kt
flutter-plugin/android— implementsFlutterPlugin+MethodCallHandler+ActivityAware. EveryonMethodCallbranch does nothing but unpack arguments and call the matchingAarambhSDKmethod. -
AarambhSDK.getInstance()
android/sdk— the real implementation. -
SDKActivity (the SDK's own UI) — or a direct REST call
Acquiring a session
Before your app calls login(), your own backend creates the agent and
acquires a session token, using two REST endpoints on Aarambh's integrations API. These are server-to-server
calls — call them from your backend, not from the mobile app.
x-access-key and x-secret-key authenticate your backend to this API and must
never ship inside your mobile app — anyone who extracts them from an APK could call these endpoints as
you. Only the resulting session token ever reaches the app.
1. Create the user
Registers the agent against your subscriber account. Only needs to run once per agent.
curl --location 'https://integrations.lsp.aarambh.cloud/api/v1/lsp/integrations/create_user' \
--header 'subscriberid: {{subscriberid}}' \
--header 'x-access-key: {{x-access-key}}' \
--header 'x-secret-key: {{x-secret-key}}' \
--header 'Content-Type: application/json' \
--data '{
"firstName": "shashank",
"lastName": "rao",
"profilePic": "",
"gender": "male",
"mobile": "7892506686",
"addressDetails": {
"location": { "type": "Point", "coordinates": [77.5946, 12.9716] },
"pincode": "560001",
"city": "Bangalore"
},
"verified": true,
"enabled": 1
}'
2. Acquire a session
Returns a session token for that mobile number — call this whenever your app needs a
fresh authToken to hand to the SDK (e.g. on each login, not just once at registration).
curl --location 'https://integrations.lsp.aarambh.cloud/api/v1/lsp/integrations/aquire_session' \
--header 'subscriberid: {{subscriberid}}' \
--header 'x-access-key: {{x-access-key}}' \
--header 'x-secret-key: {{x-secret-key}}' \
--header 'Content-Type: application/json' \
--data '{
"mobile": "7892506686",
"notifyToken": "<FCM device token for push notifications>"
}'
Hand the session token from the response to AarambhSdk.login(authToken:
...) in your Flutter app to route straight to the SDK's home screen, skipping its own OTP UI —
see API reference below.
Prerequisites
Match these against your Flutter app's android/ config — the SDK module
(android/sdk) is built with:
flutter-plugin/android/build.gradle itself is currently pinned to
compileSdk 35 (one behind the SDK module's 36) — bump it to 36 if you hit a compileSdk mismatch
warning or error when building against a Flutter app whose own compileSdk is 36.
On a recent Flutter SDK (3.44+), the Flutter engine embedding requires kotlin-stdlib:2.2.20
across every module in your app's Gradle build, regardless of what a plugin subproject's own buildscript
declares for its compiler version — flutter-plugin/android/build.gradle's
kotlin-gradle-plugin classpath is set to 2.2.20 for this reason. Your Flutter
app's own android/settings.gradle (or .kts) needs to declare a matching or newer
org.jetbrains.kotlin.android version.
Your Flutter app also needs the standard Android embedding v2 (the default for any app created with a current Flutter SDK) — this plugin does not support the legacy v1 embedding.
1. Add the dependency
Not yet published to pub.dev. For local integration, point at it by path in your Flutter app's
pubspec.yaml:
dependencies:
aarambh_sdk:
path: <PATH_TO_SDK_REPO>/flutter-plugin
Resolving the native com.aarambh:sdk dependency
flutter-plugin/android/build.gradle depends on com.aarambh:sdk:0.1.0-local, which
isn't published anywhere either. Your Flutter app's top-level
android/settings.gradle needs a composite build that substitutes it with the real module from the
SDK repo:
includeBuild('<PATH_TO_SDK_REPO>/android') {
dependencySubstitution {
substitute module('com.aarambh:sdk') using project(':sdk')
}
}
android/sdk/build.gradle in the SDK repo needs a group/version for
that substitution to resolve — if they're not already present, add:
group = 'com.aarambh'
version = '0.1.0-local'
Then run:
# from your Flutter app
flutter pub get
Building your app
If your Flutter app's own Gradle/AGP/Kotlin versions are pinned down to match the SDK's
android/ config (a recent flutter create scaffolds newer defaults than its AGP
8.7.2), Flutter will warn that support for those versions "will soon be dropped." Expected here,
not a misconfiguration — pass a flag to suppress it:
flutter run --android-skip-build-dependency-validation
flutter build apk --android-skip-build-dependency-validation
The composite build resolves flutter-plugin → com.aarambh:sdk →
the SDK's own UI assets transitively.
Reference sample app
We maintain a reference Flutter sample app (and a native Kotlin equivalent) that
demonstrates every step above end-to-end — a real Flutter app wired to flutter-plugin via a
pubspec.yaml path dependency, exercising every AarambhSdk method from a UI,
including the full headless OTP/onboarding wizard. Ask your integration contact for access if your own
composite-build wiring isn't resolving.
2. Manifest, Firebase, and permissions
flutter-plugin/android/src/main/AndroidManifest.xml itself declares nothing but an empty
<application /> — everything the SDK needs (SDKActivity, a
FileProvider, an FCM MessagingService override) is declared in
android/sdk's own manifest and merges in automatically through the Gradle module dependency. You
shouldn't need to add any of that manually.
Manifest merge
Your app's merged manifest should contain SDKActivity,
com.aarambh.plugins.background.OverlayService, MyFirebaseMessagingService, and the
location/foreground-service permissions, pulled in transitively via
capacitor-background-location. If any are missing, the composite build substitution didn't pick
up the dependency correctly.
The moment SDKActivity boots, it registers for push notifications, which requires a
FirebaseApp to already exist in your app's process — without one, the process throws
IllegalStateException: Default FirebaseApp is not initialized.
real push notifications. Add your own google-services.json (registered for
your applicationId — ours is tied to a specific one and won't match yours) and apply
both com.google.gms.google-services and
com.google.firebase.crashlytics. The latter is needed because com.aarambh:sdk
transitively pulls in @capacitor-firebase/crashlytics, whose registrar is always-eager and
crashes with The Crashlytics build ID is missing the instant any FirebaseApp
initializes, unless that plugin has run at build time to inject it. It's a separate plugin from
google-services and needs no real Firebase project itself, just the classpath.
Runtime permissions
Manifest entries are necessary but not sufficient. Your Flutter app still needs to
request location/notification/camera permissions at runtime (Android 6+) before the SDK's background tracking
will actually start — either handle this yourself, or call requestPermissions() to trigger the
SDK's own permission UI.
API reference — AarambhSdk
flutter-plugin exposes the same full surface as the native dummy app's
MainActivity.kt — every public method on native AarambhSDK has a matching
MethodChannel case and Dart method, split into two groups.
Pass-through / dispatch methods
Return Future<void>, resolve with null on success, throw
PlatformException on failure (e.g. launch() called with no attached
Activity throws code NO_ACTIVITY).
| Dart method | Native call | Notes |
|---|---|---|
initialize(config) |
AarambhSDK.initialize() |
Call once at startup, before any other call. See config keys below. |
login({authToken, userDetails}) |
AarambhSDK.login() |
Hands over an existing session instead of driving the SDK's own OTP flow. |
launch() |
AarambhSDK.launch(activity) |
Starts SDKActivity. Throws NO_ACTIVITY with no attached Flutter
Activity. |
close() |
AarambhSDK.close() |
Finishes the currently active SDKActivity, if any. |
requestPermissions() |
AarambhSDK.requestPermissions() |
Triggers the SDK's step-by-step permission workflow. Starts SDKActivity if not already
running. |
Onboarding wizard methods
Direct REST calls, no WebView/SDKActivity/SDK UI ever shown. Return
Future<Map<String, dynamic>> on success, throw PlatformException with
code SDK_ERROR on failure. initialize() must be called first — every one of these
reads clientUri/subscriberId from it. Your app is responsible for obtaining the
OTP and the agent's agentId through your own backend before calling these.
| Dart method | Native call | Notes |
|---|---|---|
verifyOtp({mobile, otp}) |
AarambhSDK.verifyOtp() |
Verifies an OTP your app already obtained, establishing a real session; a later launch() picks it up automatically. |
selectVehicle({agentId, vehicle, weight}) |
AarambhSDK.selectVehicle() |
Wizard step 2. weight is a host-app-owned lookup value. |
uploadDocument({base64Data, mimeType, fileType}) |
AarambhSDK.uploadDocument() |
Shared upload for every wizard photo step. Response has location + echoed
fileType. |
submitVehicleInfo({agentId, fields, uploads}) |
AarambhSDK.submitVehicleInfo() |
Wizard step 3. uploads maps fileType → URL from uploadDocument(). |
submitBankDetails({agentId, fields}) |
AarambhSDK.submitBankDetails() |
Final step. On success, clears the native session — expect to need OTP again. |
initialize() config keys
await AarambhSdk.initialize({
'environment': 'sandbox', // 'sandbox' | 'live' — defaults to 'live' if omitted
'subscriberId': 'my-subscriber-id',
'appname': 'My Flutter Host App',
// optional branding:
// 'vendorCode': '...',
// 'clientLogo': '...',
// 'privacy': 'https://...',
// 'terms': 'https://...',
// 'overlayIconUrl': 'https://.../icon.png',
});
clientUri/sseUriare not accepted from the host app — resolved internally fromenvironment, always overwriting anything passed under those keys.environmentdefaults toliveif omitted, so a forgetful integration fails safe to production rather than silently landing on sandbox.overlayIconUrlneeds more than just being present in this config to actually show up — it only reaches the native overlay bubble once the SDK's UI reaches its home screen with a backend-accepted session (a fake/expired token that gets rejected never gets there). It's then fetched and cached lazily the first time the bubble is shown.
start(), stop(), setUser(), acceptOrder(),
rejectOrder(), and login()/logout() all forward to whatever screen
currently owns that logic inside SDKActivity. If SDKActivity isn't currently
running when you call one of these, the call is silently dropped — not queued, no error. Call
launch() first, or make sure SDKActivity is already visible.
Usage example
import 'package:aarambh_sdk/aarambh_sdk.dart';
import 'package:flutter/services.dart';
Future<void> setupAarambhSdk() async {
// Call once at startup — BEFORE login()/launch()/any other call.
// Values are only read when SDKActivity starts up.
await AarambhSdk.initialize({
'environment': 'sandbox',
'subscriberId': 'my-subscriber-id',
'appname': 'My Flutter Host App',
});
}
Future<void> loginExistingSession(String authToken) async {
// Your app's own login/claim-session API already returned this
// authToken - hand it to the SDK to route straight to home, skipping
// its own OTP UI. userDetails is optional.
await AarambhSdk.login(authToken: authToken);
}
Future<void> openSdkUi() async {
try {
await AarambhSdk.launch(); // starts the visible SDKActivity
} on PlatformException catch (e) {
if (e.code == 'NO_ACTIVITY') {
// launch() needs a widget/screen context, not a background isolate.
}
rethrow;
}
}
// Once SDKActivity is showing (e.g. triggered from elsewhere in your app):
Future<void> startTracking() => AarambhSdk.start();
Future<void> stopTracking() => AarambhSdk.stop();
Future<void> acceptOrder(String orderId) => AarambhSdk.acceptOrder(orderId);
Future<void> rejectOrder(String orderId) => AarambhSdk.rejectOrder(orderId);
Future<void> signOut() async {
await AarambhSdk.close(); // finish SDKActivity first, if open
await AarambhSdk.logout();
}
// Onboarding wizard - no SDK UI ever shown, runs over native HTTP. Your
// app owns 100% of its own login/onboarding screens; it's responsible
// for obtaining the OTP and the agentId itself (e.g. from your own
// backend) before calling into this wizard.
Future<void> onboardingExample(String mobile, String otp, String agentId) async {
await AarambhSdk.verifyOtp(mobile: mobile, otp: otp); // establishes a real session
await AarambhSdk.selectVehicle(agentId: agentId, vehicle: 'bike', weight: 5.0);
final upload = await AarambhSdk.uploadDocument(
base64Data: '<base64-encoded-photo>',
mimeType: 'image/jpeg',
fileType: 'aadhaarProof',
);
final documentUrl = upload['location'] as String;
await AarambhSdk.submitVehicleInfo(
agentId: agentId,
fields: {'vehicleNumber': 'KA01AB1234', 'brandName': 'Honda'},
uploads: {'aadhaarProof': documentUrl},
);
// Final step — clears the session on success, so a real host app
// would route back to login here.
await AarambhSdk.submitBankDetails(
agentId: agentId,
fields: {
'accountHolderName': 'Test Driver', 'accountNumber': '123456789012',
'bankName': 'Test Bank', 'branchName': 'Test Branch',
'IFSCcode': 'TEST0001234', 'bankStatement': documentUrl,
},
);
}