Integration Guide
Integrate CoinmeBridge in Your Android App
Add Coinme's cryptocurrency purchase flow to your native Android app in under 10 minutes.
TL;DR
import com.coinme.bridge.*
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
)
val fragment = CoinmeFragment.newInstance(
orchestratorUrl = "https://widget.coinme.com",
config = config,
)
fragment.listener = this
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commit()Read on for the full step-by-step walkthrough.
Prerequisites
- Android API 24+ (Android 7.0) minimum SDK
- Kotlin 2.2+
- Your rampId and partnerKey (provided by Coinme)
- Your Orchestrator URL (e.g.
https://widget.coinme.com)
Step 1: Add the Dependency
Add the Cloudsmith Maven repository to your project-level settings.gradle.kts (your Coinme integration specialist will provide the entitlement token):
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.cloudsmith.io/coinme/coinme-sdk-frontend/")
credentials {
username = "token"
password = "<ENTITLEMENT_TOKEN>"
}
}
}
}Then add the dependency to your app module's build.gradle.kts:
dependencies {
implementation("com.coinme:coinme-bridge:1.0.0")
}Step 2: Add Manifest Permissions
Add these to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
Runtime permissions:The SDK requests camera, location, and photo library permissions automatically when the fragment is displayed. Partners only need to declare them in the manifest.
Step 3: Configure the Hosting Activity
The Activity that hosts CoinmeFragment must declare android:configChanges so that the WebView is not destroyed when the keyboard opens or the device rotates:
<activity android:name=".BuyActivity" android:configChanges="orientation screenSize|keyboardHidden|keyboard|screenLayout|smallestScreenSize" />
Why?Without this, Android destroys and recreates the Activity on configuration changes. This destroys the WebView and loses the Orchestrator session state, causing UI issues such as sheets opening unexpectedly or inputs losing focus.
Step 4: Create a Configuration
Build a CoinmeConfig with your credentials:
import com.coinme.bridge.CoinmeConfig
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
environment = "prod", // "dev", "stage", or "prod"
destinationCurrency = "BTC", // optional: pre-select crypto
)Step 5: Implement the Listener
Implement CoinmeListener to receive events and errors:
import com.coinme.bridge.*
class BuyActivity : AppCompatActivity(), CoinmeListener {
override fun onCoinmeEvent(fragment: CoinmeFragment, event: CoinmeEvent) {
when (event) {
is CoinmeEvent.TransactionComplete -> {
Log.d("Coinme", "Transaction succeeded")
}
is CoinmeEvent.TransactionFailed -> {
Log.d("Coinme", "Transaction failed")
}
is CoinmeEvent.UserCancelled -> {
finish()
}
is CoinmeEvent.KycRequired -> {
Log.d("Coinme", "KYC verification required")
}
is CoinmeEvent.SheetOpened -> { }
is CoinmeEvent.SheetClosed -> { }
is CoinmeEvent.Unknown -> {
Log.d("Coinme", "Unhandled event: ${event.type}")
}
}
}
override fun onCoinmeError(fragment: CoinmeFragment, error: CoinmeError) {
Log.e("Coinme", "Bridge error: ${error.message}")
finish()
}
}Step 6: Add the Fragment
Add a container to your layout:
<!-- activity_buy.xml -->
<FrameLayout
android:id="@+id/coinme_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />Then add the fragment:
val fragment = CoinmeFragment.newInstance(
orchestratorUrl = "https://widget.coinme.com",
config = config,
)
fragment.listener = this
supportFragmentManager.beginTransaction()
.replace(R.id.coinme_container, fragment)
.commit()That's it. CoinmeFragment handles the WebView, bridge handshake, and session lifecycle automatically.
Minimal Complete Example
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.coinme.bridge.*
class BuyActivity : AppCompatActivity(), CoinmeListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_buy)
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
destinationCurrency = "BTC",
)
val fragment = CoinmeFragment.newInstance(
orchestratorUrl = "https://widget.coinme.com",
config = config,
)
fragment.listener = this
supportFragmentManager.beginTransaction()
.replace(R.id.coinme_container, fragment)
.commit()
}
override fun onCoinmeEvent(fragment: CoinmeFragment, event: CoinmeEvent) {
when (event) {
is CoinmeEvent.TransactionComplete -> finish()
is CoinmeEvent.UserCancelled -> finish()
else -> { }
}
}
override fun onCoinmeError(fragment: CoinmeFragment, error: CoinmeError) {
Log.e("Coinme", "Error: ${error.message}")
finish()
}
}Customization
Permission Flags
Camera and location default to true to provide the full Coinme experience. Screen capture protection is on by default (allowsCapture = false).
| Flag | Default | What it controls | Manifest permissions required |
|---|---|---|---|
allowsCamera | true | Camera access for KYC document/selfie capture | CAMERA |
allowsLocation | true | Browser geolocation API (e.g. store finders) | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION |
allowsCapture | false | When false, FLAG_SECURE prevents screen recording and screenshots | None |
To opt out:
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
allowsCamera = false, // Disables camera access
allowsLocation = false, // Disables navigator.geolocation in the WebView
allowsCapture = true, // Disables screen capture protection
)
Trade-offs of disabling permissions:
allowsCamera = false: Identity-document workflows are disabled. Your widget integration will be unavailable in jurisdictions that require documentary KYC.allowsLocation = false: Users will not see nearby retail locations automatically and must specify their location manually.
Important:You need to communicate your decision about camera access and geolocation to your Coinme business development team so that your ramp can be properly configured. Not doing so may result in poor UX for a subset of your customers.
Theme and Language
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
theme = CoinmeTheme.DARK, // LIGHT (default) or DARK
language = "es", // BCP 47 tag; defaults to "en"
)Transaction Pre-fill
Lock fields to prevent user changes:
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
walletAddress = "bc1q...",
walletLock = true,
sourceAmount = 100.0,
sourceAmountLock = true,
destinationCurrency = "BTC",
destinationCurrencyLock = true,
)SSO / Authentication Passthrough
Pass credentials at any time — they're queued until the session is ready:
fragment.setAuth(
token = "jwt-token",
userId = "user-123",
userEmail = "[email protected]",
)Metadata and Tracking
val config = CoinmeConfig(
rampId = "your-ramp-id",
partnerKey = "your-partner-key",
metadata = CoinmeMetadata(
source = "android-app",
campaign = "summer-promo",
),
externalSessionId = "session-abc",
externalTransactionId = "tx-456",
)Optional Listener Methods
These have default no-op implementations:
override fun onCoinmeSessionReady(fragment: CoinmeFragment) {
// Bridge handshake complete, session is active
}
override fun onCoinmeSessionEnded(fragment: CoinmeFragment) {
// Orchestrator signaled session end
finish()
}Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Bridge never connects | Wrong Orchestrator URL or environment | Verify the URL matches your environment (dev, stage, prod) |
| Events not received | Listener not set, or set after fragment added | Set listener before adding the fragment to the activity |
| Camera not working | Missing manifest permission or runtime denial | Add CAMERA permission and handle runtime permission request |
| Blank white screen | Network or URL error | Check onCoinmeError for WebViewLoadFailed and inspect the underlying error |
| Screen capture blocked | allowsCapture defaults to false | Set allowsCapture = true in config if capture is needed |
| KYC document upload fails | Missing CAMERA or photo library permission | Add CAMERA, READ_MEDIA_IMAGES (API 33+), and READ_EXTERNAL_STORAGE (API 32 and below) to manifest |
| Photo picker shows empty | Missing READ_MEDIA_IMAGES / READ_EXTERNAL_STORAGE permission | Add photo library permissions to manifest (the SDK requests them at runtime) |
| Sheets misbehave / inputs lose focus | Activity missing android:configChanges | Add the full android:configChanges attribute (orientation, screenSize, keyboard, etc.) to the hosting Activity — see Step 3 |
| Links open blank page | target="_blank" links open externally | This is expected behavior — external links open in the device browser |
Debugging with Chrome DevTools
In debug builds the SDK enables WebView debugging automatically. To inspect:
- Open Chrome on your computer
- Navigate to
chrome://inspect - Find your device and tap Inspect next to the WebView
- Use the console to see
CoinmeBridgelog messages and Orchestrator JavaScript output
Bridge Protocol Reference
The SDK handles all protocol details internally. This section is for reference only.
Message Flow
┌─────────────┐ ┌──────────────┐
│ Native │ │ Orchestrator │
│ App │ │ (WebView) │
└──────┬──────┘ └──────┬───────┘
│ │
│ 1. Load Orchestrator URL │
│────────────────────────────────────────>│
│ │
│ 2. Handshake │
│<────────────────────────────────────────│
│ { type: "handshake" } │
│ │
│ 3. HandshakeAck │
│────────────────────────────────────────>│
│ { type: "handshakeAck" } │
│ │
│ 4. bridge-session-ready │
│<────────────────────────────────────────│
│ { type: "event" } │
│ │
│ 5. updateConfig │
│────────────────────────────────────────>│
│ │
│ 6. mount │
│────────────────────────────────────────>│
│ │
Key Differences from iOS
- Message transport: Android uses
WebViewCompat.addWebMessageListenerwith platform-enforced origin validation (falls back to@JavascriptInterfaceon older WebView versions) instead ofWKScriptMessageHandler - Capture protection: Android uses
FLAG_SECUREon the Activity window instead of the iOSUITextFieldsecure container - Embedding: Fragment-based instead of ViewController-based
- File uploads: Handled via
WebChromeClient.onShowFileChooser+ActivityResultLauncherinstead ofWKUIDelegate
Updated 18 days ago