UrbanDrive SDK Integration Guide (iOS & Android)
This guide covers integrating the UrbanDrive Flutter SDK into your native iOS or Android app. The SDK runs a Flutter Engine with the Dart entrypoint urbanDriveMain and communicates via MethodChannels.
1. Core Concepts
1.1 Architecture: Flutter "Add to App"
The SDK operates by initializing a Flutter Engine and executing a Dart entrypoint named urbanDriveMain within the native host application. This architecture ensures the entire UrbanDrive UI is displayed as a single, ready-to-use Flutter screen.
1.2 Data Exchange (Method Channels)
Configuration and user data are communicated between the native host and the Flutter SDK using MethodChannels:
| Channel Name | Direction | Purpose | Key Methods |
|---|---|---|---|
| ud/sdk | Native → Flutter | Bootstrap data retrieval (Config/User) | getBootstrap |
| ud/sdk | Flutter → Native | SDK callbacks and communication | onClientInvalid, close(), onCreateOrder |
Host App Responsibilities (Flutter Callbacks)
The native host application must implement handlers for the following methods on the ud/sdk MethodChannel to correctly manage the SDK lifecycle and respond to events:
| Method Name | Direction | Description | Parameters | Native Action Required |
|---|---|---|---|---|
| onClientInvalid | Flutter → Native | Signals that the client's session or token is invalid/expired. | Map<String, dynamic>? (Optional info about error) | Trigger a logout, refresh token, or show an alert to the user. |
| onCloseSDK (via close()) | Flutter → Native | Requests the host app to close the currently running Flutter UI. | None | iOS: Call dismiss(animated: true) on the FlutterViewController. Android: Call finish() on the FlutterActivity. |
| onCreateOrder | Flutter → Native | Signals that a new order was successfully created within the SDK. | Map<String, dynamic> orderData (Order details) | Update host app state, notify user, or perform navigation. |
2. Common Parameters
The SDK requires two main objects, config and user, to initialize on both platforms.
2.1 config (Required)
Based on the UDConfig class definition, the configuration object includes the following parameters:
| Parameter | Type | Default Value | Required | Description |
|---|---|---|---|---|
| clientId | String | — | Yes | Unique identifier for the client application. |
| env | String | "prod" | No | The environment to target ("dev" or "prod"). |
| locale | String | "uz" | No | Application language setting ("uz", "ru", "en"). |
| themeMode | String | "system" | No | UI theme preference ("light", "dark", "system"). |
| token | String | — | No | Optional authentication token for session validation. |
| initialRoute | String | — | No | Defines the initial page to open within the SDK UI. Examples: /my-orders or /order-details/:id. |
2.2 user (Required)
All fields listed below are required for the user object.
| Field | Description |
|---|---|
| firstName | User's first name. |
| lastName | User's last name. |
| midName | User's middle name (patronymic). |
| address | Residential address. |
| dob | Date of Birth (YYYY-MM-DD). |
| gender | 1 for Male, 2 for Female. |
| phone | Phone number (e.g., +998901234567). |
| pin | Personal Identification Number (PIN). |
| passport | Passport series and number. |
| docType | Document Type (0-Passport, 1-ID Card, 2-Military ID, 3-Driver License). |
| pportIssuePlace | Place of passport issuance. |
| pportIssueDate | Passport issuance date (YYYY-MM-DD). |
| pportExprDate | Passport expiration date (YYYY-MM-DD). |
| regionId, districtId | Region and District IDs. |
3. Android SDK Full Example (Kotlin)
3.1 SDK Setup (Recommended)
Add repositories and dependencies as follows.
1) Add repositories (settings.gradle.kts)
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
// UrbanDrive SDK repository (example: local host output repo)
maven(uri("/Users/dilshodbek/StudioProjects/urban_drive_sdk/build/host/outputs/repo"))
// Flutter engine artifacts
maven("https://storage.googleapis.com/download.flutter.io")
}
}2) Add dependencies (app/build.gradle.kts)
dependencies {
debugImplementation("com.urbandrive.urban_drive_sdk:flutter_debug:1.0") { isChanging = true }
releaseImplementation("com.urbandrive.urban_drive_sdk:flutter_release:1.0") { isChanging = true }
}3.2 IMPORTANT: PDF / external link opening (Android 11+) — add <queries>
If the SDK needs to open PDF files or external http/https URLs (browser / PDF viewer), Android 11+ may require package visibility rules. Add this inside AndroidManifest.xml → <manifest>:
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
</queries>If your project already has <queries>, just add these intents to the existing block.
3.3 FlutterActivity + Entrypoint (AndroidManifest)
To ensure the SDK runs the correct Flutter entrypoint (urbanDriveMain) and handles configuration changes properly, declare FlutterActivity in your host app manifest. Add this inside AndroidManifest.xml → <application>:
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:exported="false"
android:hardwareAccelerated="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode">
<meta-data
android:name="io.flutter.Entrypoint"
android:value="urbanDriveMain" />
</activity>If you already have a FlutterActivity declared, only add/merge the meta-data and ensure configChanges covers the listed values.
3.4 Integration Code (MainActivity.kt)
This code demonstrates providing bootstrap data and implementing handlers for all Flutter callbacks (onCloseSDK, onClientInvalid, onCreateOrder).
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
import android.os.Handler
import android.os.Looper
class MainActivity : ComponentActivity() {
private val ENGINE_ID = "ud_engine"
private val CHANNEL = "ud/sdk"
// ... (onCreate and UI setup) ...
private fun openUrbanDrive() {
val engine = FlutterEngineCache.getInstance().get(ENGINE_ID) ?: FlutterEngine(this)
val messenger = engine.dartExecutor.binaryMessenger
// 1. MethodChannel handler (handle callbacks)
MethodChannel(messenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"getBootstrap" -> {
// Return config and user payload
val config = mapOf(
"env" to "dev", "locale" to "uz", "clientId" to "...",
"initialRoute" to "/order-details/...")
val user = mapOf( /* ... user data ... */ )
result.success(mapOf("config" to config, "user" to user))
}
"onCloseSDK" -> {
// ACTION: Close the Flutter Activity
Toast.makeText(this, "SDK requested close", Toast.LENGTH_LONG).show()
val activityToClose = FlutterEngineCache.getInstance().get(ENGINE_ID)
?.activityControlSurface
?.getCurrentActivity()
activityToClose?.finish()
result.success(null)
}
"onClientInvalid" -> {
// ACTION: Handle expired session / invalid client
Toast.makeText(this, "Session Expired: Client Invalid", Toast.LENGTH_LONG).show()
result.success(null)
}
"onCreateOrder" -> {
// ACTION: Handle order creation event
val orderData = call.arguments as? Map<*, *>
Toast.makeText(this, "Order Created! ID: ${orderData?.get("id")}", Toast.LENGTH_LONG).show()
result.success(null)
}
else -> result.notImplemented()
}
}
// 2. Run the Dart entrypoint (delayed to ensure the engine is ready)
Handler(Looper.getMainLooper()).postDelayed({
engine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
io.flutter.FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"urbanDriveMain"
)
)
}, 500)
FlutterEngineCache.getInstance().put(ENGINE_ID, engine)
// 3. Launch the Flutter Activity
val intent = FlutterActivity.withCachedEngine(ENGINE_ID).build(this)
startActivity(intent)
}
}For detailed steps on initializing the Flutter Engine and running a cached entrypoint, refer to the official Flutter documentation: Add a Flutter screen to a native Android app.
4. iOS SDK Full Example (Swift)
4.1 SDK Setup
Format: .xcframework file.
- Add Framework: Copy the .xcframework into your Xcode project and set to "Embed & Sign".
- Update Podfile: Ensure
use_frameworks!,use_modular_headers!are present, and include the Flutter Engine dependency.
4.2 Integration Code (FlutterBridge.swift)
This code demonstrates using a Singleton to manage the FlutterEngine and handle all MethodChannel communications.
import Foundation
import Flutter
import UIKit
import FlutterPluginRegistrant
final class FlutterBridge {
static let shared = FlutterBridge()
let engine = FlutterEngine(name: "ud_engine")
private var channel: FlutterMethodChannel?
private init() {
engine.run(withEntrypoint: "urbanDriveMain")
GeneratedPluginRegistrant.register(with: engine)
attachMethodChannel()
}
private func attachMethodChannel() {
let window = UIApplication.shared.delegate?.window
channel = FlutterMethodChannel(name: "ud/sdk", binaryMessenger: engine.binaryMessenger)
channel?.setMethodCallHandler { [weak self] call, result in
switch call.method {
case "getBootstrap":
let config: [String: Any] = [
"env": "dev",
"locale": "ru",
"clientId" : "...",
"initialRoute":"/order-details/..."
]
let user: [String: Any] = [ /* ... user data ... */ ]
result(["config": config, "user": user])
case "onCloseSDK":
DispatchQueue.main.async {
window??.rootViewController?.presentedViewController?.dismiss(animated: true)
}
result(nil)
case "onClientInvalid":
DispatchQueue.main.async {
let alert = UIAlertController(
title: "Error",
message: "Client Invalid: Session Expired.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
window??.rootViewController?.present(alert, animated: true)
}
result(nil)
case "onCreateOrder":
if let orderData = call.arguments as? [String: Any], let orderId = orderData["id"] {
DispatchQueue.main.async {
let alert = UIAlertController(
title: "Order Created",
message: "New Order ID: \(orderId)",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
window??.rootViewController?.present(alert, animated: true)
}
}
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
}
func makeFlutterViewController() -> FlutterViewController {
return FlutterViewController(engine: engine, nibName: nil, bundle: nil)
}
}
// File: ViewController.swift (launching the SDK)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
_ = FlutterBridge.shared
// ... (UI setup) ...
}
@objc private func openUrbanDrive() {
let flutterVC = FlutterBridge.shared.makeFlutterViewController()
flutterVC.modalPresentationStyle = .fullScreen
self.present(flutterVC, animated: true)
}
}For detailed steps on setting up the FlutterEngine and presenting the screen, refer to the official Flutter documentation: Add a Flutter screen to a native iOS app.
4.3 App Store Connect reject fix (ITMS-91065: Missing signature)
If App Store Connect reports errors like ITMS-91065: Missing signature for commonly used third‑party SDKs (e.g. sqflite_darwin, url_launcher_ios), you need to codesign those frameworks/xcframeworks with your Apple Distribution certificate before submission.
Example (sqflite_darwin):
codesign --timestamp -v -f \
--sign "Apple Distribution: Entity name (Team ID)" \
sqflite_darwin.xcframeworkSimilarly (url_launcher_ios):
codesign --timestamp -v -f \
--sign "Apple Distribution: Entity name (Team ID)" \
url_launcher_ios.xcframework5. Testing Checklist
- Framework/.aar successfully added and linked (set to Embed & Sign for iOS).
- The Flutter UI successfully opened using the native launcher code.
- User login successful (verifies data is passed correctly via
ud/sdk). - Catalog, order, and payment screens are visible and functional.
- Callback Test:
onClientInvalidis triggered and correctly handled by the host application. - Callback Test:
onCloseSDKcloses the native Flutter container. - Callback Test:
onCreateOrderis triggered with correct data upon order creation.