Android Native
Integrate Streamoji Avatars with your Android app using the Streamoji WebView SDK.
1. Prerequisites
Before starting the integration, ensure you have:
- A Streamoji Developer Account: Register at Streamoji Dashboard.
- Android Example App (Kotlin): Reference implementation at ready-player-me-archives/Example-Android-Kotlin.
- API Credentials: You will need a
Client IDandClient Secretto fetch authentication tokens. - Android Studio: The SDK is compatible with Android API level 21 (Lollipop) and higher.
2. Project Setup
Android Permissions
Add the following permissions to your AndroidManifest.xml to enable network access, camera usage (for photo-based avatars), and file storage:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />3. The Identity Bridge (userid)
Attaching a userid to your avatar creator session is critical for mapping avatars to specific users in your backend database. It enables:
- User Identification: Know exactly who is creating the avatar.
- Persistence: Automatic saving and loading of user-specific assets.
- Re-editing: Fetching the same avatar state when the user returns.
Constructing the URL
Always use Uri.Builder to ensure the userid and other parameters are correctly URL-encoded.
val baseUrl = "https://avatars.streamoji.com/createAvatar"
val finalUrl = Uri.parse(baseUrl)
.buildUpon()
.appendQueryParameter("userid", currentUserId) // REQUIRED: The unique user identifier
.appendQueryParameter("token", authToken) // REQUIRED: Your session token
.appendQueryParameter("iframe", "true") // REQUIRED: Optimized for WebView
.appendQueryParameter("source", "android-sdk")
.appendQueryParameter("themeColor", "B8B8FC") // OPTIONAL: Custom hex color
.build()
.toString()
webView.loadUrl(finalUrl)4. WebView Configuration
To correctly process the avatar creator events, your WebView must be configured with JavaScript enabled and a bridge interface:
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
allowFileAccess = true
}
// Injects 'WebView' object into the JavaScript context
webView.addJavascriptInterface(StreamojiWebBridge(this), "WebView")5. Event Handling (JavaScript Bridge)
When iframe=true is set, the Avatar Creator emits events in two ways:
- Native bridge (recommended for WebView): calls
WebView.receiveData(jsonString)when you register aJavascriptInterfacenamedWebView. - Browser iframe: also sends
window.parent.postMessage(jsonString, '*')for web embeds.
The payload is always a JSON string with source, eventName, and optional data. Parse it before reading fields — do not treat event.data as an object.
Key Events:
| Event Name | Description |
|---|---|
v1.frame.ready | The creator is fully loaded and ready for interaction. |
v1.avatar.exported | The user clicked "Save". Returns the avatar URL and associated avatarId. |
v1.auth.expired | The session token was rejected (401). Refresh the token and reload or send setToken. |
Handling the Export Event:
@JavascriptInterface
fun receiveData(json: String) {
val message = Gson().fromJson(json, WebMessage::class.java)
when (message.eventName) {
"v1.frame.ready" -> {
Log.d("Streamoji", "Creator ready")
}
"v1.avatar.exported" -> {
val avatarUrl = message.data["url"]
val avatarId = message.data["avatarId"]
Log.d("Streamoji", "Avatar Exported: $avatarId")
// Proceed with loading the .glb or .png avatar
}
"v1.auth.expired" -> {
Log.w("Streamoji", "Auth expired — refresh token")
}
}
}6. .NET MAUI / Hybrid WebView
On Android, register a JavascriptInterface named WebView with a receiveData(string) method (same contract as Kotlin). That is the reliable path for native callbacks.
If you instead inject a window message listener via EvaluateJavaScriptAsync, you must JSON.parse the payload first. The creator sends a string, not an object:
window.addEventListener('message', function(event) {
var data = event.data;
if (typeof data === 'string') {
try { data = JSON.parse(data); } catch (e) { return; }
}
if (!data || data.source !== 'streamojiavatars') return;
if (data.eventName === 'v1.frame.ready') {
console.log('FRAME READY FIRED');
}
if (data.eventName === 'v1.avatar.exported') {
console.log('EXPORT FIRED: ' + JSON.stringify(data.data));
}
});Inject the listener before or as the page loads. If you inject after navigation, you may miss v1.frame.ready; v1.avatar.exported still fires on Save. Prefer the WebView.receiveData bridge so events reach C# without relying on same-window postMessage or console logging.
7. Best Practices
- Security: Never hardcode your
Client Secretin the Android app. Always fetch theauthTokenthrough your own secure backend API. - Encoding: Always use
Uri.Builderfor theuserid. If a user ID is an email (e.g.,user@example.com), standard string concatenation will fail to load the creator correctly. - Loading States: Display a
ProgressBaruntil thev1.frame.readyevent is received to ensure a premium user experience. - Parse JSON strings: Bridge and
postMessagepayloads are JSON strings. Always parse before readingeventName/data.