zero_network_kit
English | 简体中文
A Flutter plugin for network diagnostics: connectivity inspection, latency probing, DNS resolution, port checks, bandwidth measurement, quality scoring and micro-benchmarks — for Android, iOS, macOS, Windows, Linux and Web (partial).
🔔 Upgrade recommended:
1.0.1adds partial Web support — the plugin now compiles and runs in the browser, and the capabilities the sandbox forbids degrade gracefully instead of failing. It also fixes the speed test on the web. Upgrade to^1.0.1.
🌐 Official Website · 📦 View on pub.dev · 🔗 View on GitHub
Table of Contents
- Features
- Getting started
- Usage
- Testing your own code against it
- Platform support
- Documentation
- License
Features
| Capability | API | Notes |
|---|---|---|
| Connectivity | NetworkDiagnostic.checkConnection() |
Transport, IPv4/IPv6, gateway, SSID, RSSI, MAC, VPN |
| Connectivity stream | NetworkDiagnostic.onConnectivityChanged |
Emits a fresh snapshot on every change |
| Latency | NetworkDiagnostic.ping() |
TCP handshake RTT everywhere; system ICMP on desktop |
| DNS | NetworkDiagnostic.resolve() |
system resolver + raw UDP against explicit servers |
| Bandwidth | NetworkDiagnostic.runSpeedTest() |
Download/upload throughput with progress callbacks |
| Ports | NetworkDiagnostic.checkPort() / scanPorts() |
Bounded-concurrency TCP reachability |
| Quality | NetworkDiagnostic.evaluateQuality() |
Weighted 0–100 score + level + suggestions |
| Full report | NetworkDiagnostic.diagnose() |
One-shot aggregate of every probe |
| Capabilities | NetworkDiagnostic.capabilities |
Query-then-call; hide unsupported cards (e.g. SSID on desktop) |
| Benchmarks | NetworkBenchmark.runAll() |
Measures how fast the diagnostics API itself runs |
Design goals:
- Hermetic unit tests — every service accepts injected collaborators
(
ConnectivityAdapter,http.Client,PingService, …), so the whole test suite runs without touching the network. - No hidden state — results are immutable value objects with
toMap(). - Graceful degradation — a missing permission or an unreachable sub-service
never throws through the public API; the corresponding field stays
null.
Getting started
dependencies:
zero_network_kit: ^1.0.1
Android permissions
The plugin manifest already declares INTERNET, ACCESS_NETWORK_STATE and
ACCESS_WIFI_STATE. Reading the Wi-Fi SSID additionally requires location
permission (ACCESS_FINE_LOCATION) on Android 8.1+ and the Access WiFi
Information entitlement plus location authorisation on iOS. Without it the
snapshot simply reports ssid: null.
Usage
The snippets below cover the common paths. For every method, parameter, default and result field, see the API guide.
import 'package:zero_network_kit/zero_network_kit.dart';
void main() {
// Optional: apply global defaults once.
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(
pingHost: '1.1.1.1',
dnsServers: <String>['1.1.1.1', '8.8.8.8'],
),
);
runApp(const MyApp());
}
Connectivity
final info = await NetworkDiagnostic.checkConnection(probeReachability: true);
print('${info.type.label} · ${info.ipAddress} · ${info.signalStrength} dBm');
print('gateway=${info.gateway} vpn=${info.isVpn} reachable=${info.isReachable}');
await for (final change in NetworkDiagnostic.onConnectivityChanged) {
print('now on ${change.type.id}');
}
Just need connectivity? You don't need
init()or any of the other APIs —checkConnection()andonConnectivityChangedwork out of the box. See the Connectivity-only: minimal example in the Connectivity chapter of API.md.
Ping
final result = await NetworkDiagnostic.ping(host: '1.1.1.1', count: 5);
print('${result.received}/${result.sent} replies, '
'loss ${result.packetLoss.toStringAsFixed(1)}%, '
'avg ${result.averageTime.toStringAsFixed(1)} ms, '
'jitter ${result.jitter.toStringAsFixed(2)} ms');
On desktop you can switch to the system ICMP binary:
await NetworkDiagnostic.ping(host: '1.1.1.1', mode: PingMode.icmp);
DNS
final results = await NetworkDiagnostic.resolve(
domain: 'example.com',
dnsServers: const <String>['1.1.1.1', '8.8.8.8'],
includeSystemResolver: true,
);
for (final r in results) {
print('${r.server}: ${r.isSuccess ? r.resolvedIps.join(", ") : r.errorMessage}'
' (${r.responseTimeMs.toStringAsFixed(1)} ms)');
}
Speed test
final speed = await NetworkDiagnostic.runSpeedTest(
onProgress: (progress) => print(
'${progress.phase.name}: ${progress.speedMbps.toStringAsFixed(1)} Mbps',
),
);
print('↓ ${speed.downloadSpeed.toStringAsFixed(2)} Mbps '
'↑ ${speed.uploadSpeed.toStringAsFixed(2)} Mbps');
Download and upload phases write a meaningful amount of traffic. Defaults point
at the public Cloudflare speed endpoints; override downloadUrl / uploadUrl
to use your own.
Ports
if (await NetworkDiagnostic.isPortOpen(host: 'example.com', port: 443)) {
print('HTTPS reachable');
}
final scan = await NetworkDiagnostic.scanPorts(
host: 'example.com',
ports: const <int>[22, 80, 443, 8080],
concurrency: 8,
);
Quality score
final score = await NetworkDiagnostic.evaluateQuality(includeSpeedTest: false);
print('${score.score}/100 (${score.level.label})');
for (final suggestion in score.suggestions) {
print('• $suggestion');
}
The score is a weighted average of the metrics that are available:
| Metric | Weight | Source |
|---|---|---|
latency |
0.25 | PingResult.averageTime |
jitter |
0.10 | PingResult.jitter |
packetLoss |
0.15 | PingResult.packetLoss |
download |
0.25 | SpeedTestResult.downloadSpeed |
upload |
0.15 | SpeedTestResult.uploadSpeed |
dns |
0.10 | mean successful DnsTestResult.responseTimeMs |
signalStrength |
0.10 | NetworkConnectionInfo.signalStrength |
Tune the ideal values with NetworkDiagnosticConfig(qualityTargets: ...).
Full report
final report = await NetworkDiagnostic.diagnose(includePorts: true);
print(report); // NetworkDiagnosticReport(type: wifi, connected: true, score: 87.5)
print(report.toMap()); // JSON encodable
Benchmarks
final suite = await NetworkBenchmark.runAll(iterations: 20, warmupIterations: 3);
for (final result in suite.results) {
print('${result.testName}: avg '
'${(result.averageDuration.inMicroseconds / 1000).toStringAsFixed(2)} ms, '
'${result.operationsPerSecond.toStringAsFixed(1)} ops/s');
}
Benchmarks answer questions like "how expensive is one checkConnection() call
on this device?" — useful when deciding whether a diagnostic belongs on the
startup path.
Testing your own code against it
Every service is injectable, so you can unit-test your integration points without a network:
class FakeConnectivityAdapter implements ConnectivityAdapter {
@override
Future<List<String>> checkConnectivity() async => <String>['wifi'];
@override
Stream<List<String>> get onConnectivityChanged => const Stream.empty();
}
NetworkDiagnostic.configure(
connectivity: ConnectivityService(adapter: FakeConnectivityAdapter()),
);
NetworkDiagnostic.reset() restores the built-in defaults.
Platform support
| Platform | Status |
|---|---|
| Android | ✅ Supported (Kotlin native side) |
| iOS | ✅ Supported (Swift native side) |
| macOS | ✅ Supported (Swift native side) |
| Windows | ✅ Supported (C++ native side) |
| Linux | ✅ Supported (C++ native side) |
| Web | ⚠️ Partial — see Web support below |
Web support
The web build exposes the same static API. Capabilities that the browser sandbox
forbids degrade gracefully — they return null or an "unavailable" result
instead of throwing:
| Capability | Web | Notes |
|---|---|---|
| Connectivity | ✅ | via connectivity_plus |
Ping (PingMode.tcp) |
⚠️ | HTTPS round trip; the target must send CORS headers |
Ping (PingMode.icmp) |
❌ | throws UnsupportedError |
| DNS (system resolver) | ✅ | via DNS-over-HTTPS |
| DNS (explicit server) | ⚠️ | needs a DoH endpoint, otherwise "unsupported" |
| Speed test | ✅ | HTTP download / upload |
| Quality score | ✅ | pure function |
| Benchmark | ✅ | pure function |
| Port check / scan | ❌ | returns "unavailable" results |
| Native details (SSID, gateway, MAC, VPN) | ❌ | null |
ZeroNetworkKit.getNativeNetworkDetails() returns null on the web and
ZeroNetworkKit.getPlatformVersion() returns Web. Call
NetworkCapabilities.current() to discover the supported set at runtime.
Documentation
- API.md — complete usage guide: every public API with copy-paste examples, parameter defaults, model reference and common pitfalls.
- AGENTS.md — engineering contract: architecture, conventions and the new-feature checklist.
- CONTRIBUTING.md — how to set up and submit changes.
- CHANGELOG.md — release history.
License
MPL-2.0 © Zero Labs
Third-party dependency, trademark and endorsement notices live in NOTICE.
Libraries
- advanced
- 进阶 / 注入 / 底层 API / Advanced, injectable and low-level APIs.
- zero_network_kit
- Flutter 网络诊断插件 / A Flutter plugin for network diagnostics.
- zero_network_kit_method_channel
- zero_network_kit_platform_interface