SpeedOf.Me API - Android Integration

This example demonstrates how to integrate the SpeedOf.Me speed test API into an Android app using WebView.

How It Works

The SpeedOf.Me API is JavaScript-based, so Android apps use WebView to:

  1. Load an HTML page containing the speed test
  2. Receive results via @JavascriptInterface

Files

Prerequisites

Quick Start

1. Add Files

2. Configure API Credentials

SomApi.account = "YOUR_API_KEY";
SomApi.domainName = "your-domain.com";

3. Add Dependencies

In app/build.gradle.kts:

dependencies {
    implementation("androidx.activity:activity-compose:1.8.0")
    implementation("androidx.compose.material3:material3:1.1.2")
    implementation("androidx.compose.ui:ui:1.5.4")
}

4. Add Internet Permission

<uses-permission android:name="android.permission.INTERNET" />

5. Register Activity

<activity
    android:name=".SpeedTestActivity"
    android:exported="true" />

Architecture

Android App
   └── SpeedTestActivity (Compose)
           └── WebView
                   └── speedtest.html (on your domain)
                           └── api.js
                                   ▼
                   window.Android.onMessage(json)
                                   ▼
                   @JavascriptInterface onMessage()

JavaScript-to-Kotlin Communication

The HTML page sends messages via:

window.Android.onMessage(JSON.stringify({
    type: 'completed',
    data: result
}));

Kotlin receives them via @JavascriptInterface:

class SpeedTestJsInterface(private val viewModel: SpeedTestViewModel) {
    @JavascriptInterface
    fun onMessage(json: String) {
        viewModel.handleMessage(json)
    }
}

// Add to WebView
webView.addJavascriptInterface(
    SpeedTestJsInterface(viewModel),
    "Android"  // This becomes window.Android in JS
)

Bridge Messages

speedtest.html sends five message types: ready, started, progress, completed and error. It normalizes the numbers before forwarding them, because the engine sends an empty string for any value that is not available, which would break Kotlin's typed parsing:

function num(v) { return typeof v === 'number' ? v : 0; }

progress payload, forwarded as {type, pass, percentDone, currentSpeed}:

speedtest.html sets SomApi.config.progress.verbose = true, which is what makes currentSpeed a real number during the download and upload phases.

completed payload, forwarded as {download, upload, latency, jitter, testServer, ip_address, hostname}. The engine's full result also carries maxDownload, maxUpload, userAgent and testDate; add them to the forwarded object and to SpeedTestResult if you need them. Every field is always present: a disabled sub-test sends an empty string rather than omitting the key, which is why the numbers go through num().

error payload is {code, message}:

Code 2006, and code 2005 with status 0, mean the request never left the WebView, normally a content blocker or a frame-src Content-Security-Policy that does not allow https://speedof.me. A 2005 carrying any other status is a real HTTP response from the server, so read the status in the message.

Hosting the Test Page

Host speedtest.html on your registered domain and load it over https:

webView.loadUrl("https://your-domain.com/speedtest.html")

This is the only loading method that works. The engine validates the page that embeds api.js against the domain registered on your account, and it reads that domain from the page's own URL. A page loaded from assets/ (file:///android_asset/...), from any other file:// URL, or from an HTML string has no host, so it can never match your registration and every test fails with error 1002 (Domain Mismatch).

WebView Configuration

webView.settings.apply {
    javaScriptEnabled = true      // Required
    domStorageEnabled = true      // Recommended
    mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
}

ProGuard Rules

-keepclassmembers class * {
    @android.webkit.JavascriptInterface <methods>;
}

Production Considerations

  1. API Key Security: Store keys in local.properties or BuildConfig
  2. Thread Safety: @JavascriptInterface methods run on a background thread
  3. Memory Management: Call webView.destroy() in onDestroy()
  4. SSL/TLS: Modern Android requires HTTPS

Troubleshooting

Links