SpeedOf.Me API - Flutter Integration

This example demonstrates how to integrate the SpeedOf.Me speed test API into a Flutter app using webview_flutter.

How It Works

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

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

Files

Prerequisites

Quick Start

1. Add Dependencies

# pubspec.yaml
dependencies:
  webview_flutter: ^4.4.2

2. Platform Setup

Android (AndroidManifest.xml):

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

iOS: no extra Info.plist entry is needed. (The old io.flutter.embedded_views_preview key has been a no-op since Flutter 1.22 and is not required by webview_flutter 4.x.)

3. Add the Screen

Copy speed_test_screen.dart to your project.

4. Configure API Credentials

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

5. Host the Test Page

Upload speedtest.html to your registered domain and load it over https:

..loadRequest(Uri.parse('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. loadFlutterAsset and loadHtmlString give the page no host, so they can never match your registration and every test fails with error 1002 (Domain Mismatch).

Architecture

Flutter App
   └── SpeedTestScreen (StatefulWidget)
           └── WebViewWidget (webview_flutter)
                   └── speedtest.html (on your domain)
                           └── api.js
                                   ▼
                   window.SpeedTest.postMessage(json)
                                   ▼
                   JavaScriptChannel.onMessageReceived()

JavaScript-to-Dart Communication

The HTML page sends messages via:

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

Flutter receives them via JavaScriptChannel:

..addJavaScriptChannel(
  'SpeedTest',  // This becomes window.SpeedTest in JS
  onMessageReceived: (message) {
    final json = jsonDecode(message.message);
    // Handle message
  },
)

Data Models

speedtest.html normalizes the numbers before forwarding them, because the engine sends an empty string for any value that is not available, which would break Dart's typed casts:

function num(v) { return typeof v === 'number' ? v : 0; }
class SpeedTestResult {
  final double download;
  final double upload;
  final double latency;
  final double jitter;
  final String? testServer;
  final String? ipAddress;
  final String? hostname;
}

class SpeedTestProgress {
  final String type;         // "download", "upload" or "latency"
  final double currentSpeed; // Mbps, 0 on latency events
  final double percentDone;  // 0-100
  final int pass;            // 0 during the latency phase
}

class SpeedTestError {
  final int code;
  final String message;
}

currentSpeed is only a real number when SomApi.config.progress.verbose = true, which speedtest.html sets. Without it the engine sends '' on every progress event, and it always sends '' on latency events, which num() turns into 0.

The engine's full result also carries maxDownload, maxUpload, userAgent and testDate. Add them to the forwarded object in speedtest.html 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.

Error codes

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.

State Management

For larger apps, consider:

Production Considerations

  1. API Key Security: Store keys securely (flutter_dotenv, server-side)
  2. Platform Differences: Test on both iOS and Android
  3. WebView Memory: Dispose controller properly
  4. Connectivity: Check network state before testing

Troubleshooting

Links