Every public capability of zero_network_kit, with copy‑paste ready examples.
本文件覆盖 zero_network_kit 的全部公开功能,每个示例都可以直接复制运行。
English | 简体中文
Heads‑up / 注意:our
NetworkTypeexposeslabelandid— there is nodisplayName. Writeconnection.type.label, notconnection.type.displayName. / 本插件的NetworkType只有label与id, 没有displayName。
| # | Capability / 能力 | API |
|---|---|---|
| 0 | Import & setup | ZeroNetworkKit.init() |
| 1 | Which API do I need? | — |
| 2 | Global configuration | NetworkDiagnosticConfig |
| 3 | Connectivity | checkConnection() |
| 4 | Connectivity stream | onConnectivityChanged |
| 5 | Latency / Ping | ping() |
| 6 | DNS | resolve() |
| 7 | Ports | checkPort() / scanPorts() |
| 8 | Speed test | runSpeedTest() |
| 9 | Quality score | evaluateQuality() |
| 10 | Full report | diagnose() |
| 11 | Benchmarks | NetworkBenchmark.runAll() |
| 12 | Native platform data | getPlatformVersion() |
| 13 | JSON serialisation | toMap() |
| 14 | Dependency injection & tests | configure() |
| 15 | Model reference | — |
| 16 | Gotchas | — |
import 'package:zero_network_kit/zero_network_kit.dart';That single import exposes everything. Initialisation is optional — every API falls back to built‑in defaults. 一个 import 即可;初始化是可选的,不调用也能直接用(走内置默认值)。
void main() {
// Optional. Apply your own global defaults once.
// 可选:全局定制一次默认参数。
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(pingHost: '1.1.1.1'),
);
runApp(const MyApp());
}Release the HTTP client the plugin owns when your app shuts down:
await ZeroNetworkKit.dispose();Inspecting the current state:
ZeroNetworkKit.isInitialized; // false until init() runs
ZeroNetworkKit.config; // the effective NetworkDiagnosticConfig| I want to know… | Call |
|---|---|
| Am I online, and over what transport? | NetworkDiagnostic.checkConnection() |
| React to Wi‑Fi ⇄ cellular switches | NetworkDiagnostic.onConnectivityChanged |
| Latency, jitter, packet loss | NetworkDiagnostic.ping() |
| Is DNS slow or broken? | NetworkDiagnostic.resolve() |
Is host:port reachable? |
NetworkDiagnostic.checkPort() |
| Which of these ports are open? | NetworkDiagnostic.scanPorts() |
| How fast is down/up? | NetworkDiagnostic.runSpeedTest() |
| One number for “good or bad” | NetworkDiagnostic.evaluateQuality() |
| Everything at once, as a report | NetworkDiagnostic.diagnose() |
| Is the diagnostics API itself expensive? | NetworkBenchmark.runAll() |
| Native version / SSID / gateway / MAC | ZeroNetworkKit.getPlatformVersion(), getNativeNetworkDetails() |
NetworkDiagnosticConfig centralises every default. Override only what you
need; the rest keep the defaults below.
NetworkDiagnosticConfig 集中了全部默认参数,只需覆盖你关心的字段。
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(
pingHost: '1.1.1.1',
pingPort: 443,
pingCount: 5,
dnsDomain: 'example.com',
dnsServers: <String>['1.1.1.1', '8.8.8.8'],
downloadUrl: 'https://my-cdn.example.com/speedtest.bin',
uploadUrl: 'https://my-cdn.example.com/upload',
),
);| Field | Default | Meaning |
|---|---|---|
pingHost |
'1.1.1.1' |
Default ping / port‑scan target |
pingPort |
443 |
Port used by the TCP ping |
pingCount |
4 |
Probes per ping run |
pingTimeout |
3s |
Timeout of one probe |
pingInterval |
200ms |
Delay between probes |
dnsDomain |
'www.google.com' |
Default domain for DNS tests |
dnsServers |
['1.1.1.1', '8.8.8.8', '114.114.114.114'] |
Servers queried by default |
dnsTimeout |
5s |
Timeout per DNS server |
downloadUrl |
Cloudflare __down?bytes=25000000 |
Download endpoint |
uploadUrl |
Cloudflare __up |
Upload endpoint |
uploadPayloadBytes |
1048576 (1 MiB) |
Upload payload size |
speedTestTimeout |
30s |
Per‑request timeout |
speedTestMaxDuration |
10s |
Sampling window per phase |
portCheckTimeout |
3s |
Timeout of one port check |
probePorts |
[80, 443] |
Ports used by scanPorts() by default |
qualityTargets |
const QualityTargets() |
Ideal values for scoring |
The defaults point at public Cloudflare speed endpoints. Swap them for your own before shipping production traffic. 默认测速端点是 Cloudflare 的公共服务,正式项目请替换为自建端点。
const NetworkDiagnosticConfig(
qualityTargets: QualityTargets(
excellentLatency: 30, // ms
acceptableLatency: 150, // ms
excellentJitter: 5, // ms
acceptableJitter: 40, // ms
acceptablePacketLoss: 5, // %
excellentDownload: 50, // Mbps
acceptableDownload: 5, // Mbps
excellentUpload: 20, // Mbps
acceptableUpload: 2, // Mbps
excellentDns: 30, // ms
acceptableDns: 200, // ms
),
);final connection = await NetworkDiagnostic.checkConnection();
print('type : ${connection.type.label}'); // Wi-Fi / Mobile / Ethernet …
print('connected : ${connection.isConnected}');
print('ipv4 : ${connection.ipAddress}');
print('ipv6 : ${connection.ipv6Address}');
print('gateway : ${connection.gateway}');
print('ssid : ${connection.ssid}');
print('rssi : ${connection.signalStrength} dBm');
print('mac : ${connection.macAddress}');
print('vpn : ${connection.isVpn}');
print('timestamp : ${connection.timestamp}');Parameters:
| Parameter | Default | Meaning |
|---|---|---|
includeNativeDetails |
true |
Also read SSID / gateway / MAC / VPN from the native side |
probeReachability |
false |
Additionally make a real request and fill isReachable |
probeTimeout |
3s |
Timeout of that reachability probe |
// Skip the native channel (cheapest call — good on hot paths).
final quick = await NetworkDiagnostic.checkConnection(
includeNativeDetails: false,
);
// Prove the network actually reaches the internet, not just that a NIC is up.
final verified = await NetworkDiagnostic.checkConnection(
probeReachability: true,
probeTimeout: const Duration(seconds: 5),
);
if (verified.isReachable == false) {
print('Interface is up but the internet is unreachable.');
}If all you need is "am I online, on what transport, what IP, and react to
changes", you only need sections 3 and 4 — no init() required, and you can
ignore ping / DNS / speed entirely.
如果只需要「在不在線、走什么網络、IP 是多少、切换时通知」,只用第 3、4 节即可——
无需 init(),也不必调用 ping / DNS / 测速。
One-shot snapshot (plain Dart, no widget): 一次性快照(纯 Dart,无需 Widget):
final c = await NetworkDiagnostic.checkConnection();
print('${c.type.label} · connected=${c.isConnected} · ip=${c.ipAddress}');A minimal Flutter screen that shows the current connection and updates on every switch (Wi-Fi ⇄ cellular, etc.): 一个最小的 Flutter 页面,展示当前连接并在切换时刷新(Wi-Fi ⇄ 蜂窝等):
import 'package:flutter/material.dart';
import 'package:zero_network_kit/zero_network_kit.dart';
class ConnectivityScreen extends StatelessWidget {
const ConnectivityScreen({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<NetworkConnectionInfo>(
stream: NetworkDiagnostic.onConnectivityChanged,
builder: (context, snap) {
final c = snap.data;
if (c == null) {
return const Center(child: CircularProgressIndicator());
}
return ListView(
children: [
ListTile(title: const Text('Type / 类型'), trailing: Text(c.type.label)),
ListTile(title: const Text('Online / 在线'), trailing: Text('${c.isConnected}')),
ListTile(title: const Text('IPv4'), trailing: Text(c.ipAddress ?? '—')),
ListTile(title: const Text('IPv6'), trailing: Text(c.ipv6Address ?? '—')),
],
);
},
);
}
}
ZeroNetworkKit.init()is optional and only needed to override defaults (e.g. a custom reachability host). Connectivity alone works with the built-in defaults.ZeroNetworkKit.init()是可选的,仅在想覆盖默认参数时才需要;单用连通性直接走内置默认值即可。
final subscription = NetworkDiagnostic.onConnectivityChanged.listen(
(info) => print('now on ${info.type.id} · ${info.ipAddress}'),
);
// Later:
await subscription.cancel();In a widget:
StreamBuilder<NetworkConnectionInfo>(
stream: NetworkDiagnostic.onConnectivityChanged,
builder: (context, snapshot) {
final info = snapshot.data;
if (info == null) return const Text('Checking…');
return Text('${info.type.label} · ${info.isConnected}');
},
)final ping = await NetworkDiagnostic.ping(
host: '1.1.1.1',
count: 5,
timeout: const Duration(seconds: 2),
interval: const Duration(milliseconds: 200),
port: 443,
);
print('received : ${ping.received}/${ping.sent}');
print('loss : ${ping.packetLoss.toStringAsFixed(1)} %');
print('min/avg/max: ${ping.minTime.toStringAsFixed(1)} / '
'${ping.averageTime.toStringAsFixed(1)} / '
'${ping.maxTime.toStringAsFixed(1)} ms');
print('jitter : ${ping.jitter.toStringAsFixed(2)} ms');
print('samples : ${ping.times}');| Parameter | Default | Meaning |
|---|---|---|
host |
config.pingHost |
Target host |
count |
config.pingCount |
Number of probes |
timeout |
config.pingTimeout |
Per‑probe timeout |
interval |
config.pingInterval |
Delay between probes |
port |
config.pingPort |
TCP port probed in PingMode.tcp |
mode |
PingMode.tcp |
PingMode.tcp or PingMode.icmp |
PingMode.tcp performs a TCP handshake to host:port — the portable
equivalent of ICMP, and the only mode available on Android/iOS.
PingMode.tcp 通过 TCP 握手到 host:port 测量往返,是移动端唯一可用方式。
// Desktop only — falls back gracefully if the `ping` binary is unavailable.
await NetworkDiagnostic.ping(host: '1.1.1.1', count: 4, mode: PingMode.icmp);ping.isSuccess is true when at least one probe answered; check it before
trusting the averages.
至少一次成功响应时 isSuccess 为 true,读平均值前建议先判断它。
final results = await NetworkDiagnostic.resolve(
domain: 'example.com',
dnsServers: const <String>['1.1.1.1', '8.8.8.8', '114.114.114.114'],
timeout: const Duration(seconds: 5),
concurrent: true,
includeSystemResolver: true,
);
for (final r in results) {
if (r.isSuccess) {
print('${r.server.padRight(16)} → ${r.resolvedIps.join(", ")} '
'(${r.responseTimeMs.toStringAsFixed(1)} ms)');
} else {
print('${r.server.padRight(16)} ✗ ${r.errorMessage}');
}
}| Parameter | Default | Meaning |
|---|---|---|
domain |
config.dnsDomain |
Domain to resolve |
dnsServers |
config.dnsServers |
Servers queried in parallel (or in series) |
timeout |
config.dnsTimeout |
Timeout of one query |
concurrent |
true |
Query all servers at once |
includeSystemResolver |
false |
Also add a system row |
Notes / 说明:
- A row with
server == 'system'comes from the OS resolver; the others are raw UDP queries against the listed IPs, encoded by the built‑inDnsPacketwire codec. /server == 'system'的行来自系统解析器,其余是对指定 IP 的原始 UDP 查询,由内置DnsPacket编解码。 includeSystemResolverdefaults tofalseat the facade level — passtrueto include it. / 门面层默认 不 包含系统解析器。- Compare servers to find the fastest one: / 可以借此挑出最快的 DNS:
final fastest = results
.where((r) => r.isSuccess)
.reduce((a, b) => a.responseTimeMs <= b.responseTimeMs ? a : b);
print('fastest resolver: ${fastest.server}');// Single port → boolean convenience
final open = await NetworkDiagnostic.isPortOpen(
host: 'example.com',
port: 443,
timeout: const Duration(seconds: 3),
);
print(open ? 'HTTPS reachable' : 'HTTPS unreachable');// Many ports → per-port results with RTT and error message
final scan = await NetworkDiagnostic.scanPorts(
host: 'example.com',
ports: const <int>[22, 80, 443, 8080, 8443],
concurrency: 8,
);
for (final r in scan) {
print('${r.host}:${r.port} '
'${r.isOpen ? "open" : "closed"} '
'${r.responseTimeMs.toStringAsFixed(1)} ms '
'${r.errorMessage ?? ""}');
}| Parameter | Default | Meaning |
|---|---|---|
ports (scanPorts) |
config.probePorts ([80, 443]) |
Ports to probe |
concurrency |
12 |
Max in‑flight connections |
timeout |
config.portCheckTimeout |
Timeout per port |
checkPort()returns aPortCheckResult(withisOpen,responseTimeMsanderrorMessage); useisPortOpen()for a plain boolean. When you need the round‑trip time or the failure reason for a single port, callcheckPort()directly or pass a one‑element list toscanPorts():scanPorts(host: 'example.com', ports: [443]).first.checkPort()返回PortCheckResult(含isOpen、responseTimeMs、errorMessage);单端口布尔诉求请用isPortOpen()。需要 RTT 或失败原因时 可直接调用checkPort(),或给scanPorts(..., ports: [443])取单条结果。
final speed = await NetworkDiagnostic.runSpeedTest(
includeUpload: true,
includePing: true,
onProgress: (progress) {
print('${progress.phase.name}: '
'${progress.speedMbps.toStringAsFixed(1)} Mbps '
'(${progress.bytes} bytes, ${progress.elapsed.inMilliseconds} ms)');
},
);
print('download : ${speed.downloadSpeed.toStringAsFixed(2)} Mbps');
print('upload : ${speed.uploadSpeed.toStringAsFixed(2)} Mbps');
print('ping : ${speed.ping.toStringAsFixed(1)} ms');
print('jitter : ${speed.jitter.toStringAsFixed(2)} ms');
print('loss : ${speed.packetLoss.toStringAsFixed(1)} %');
print('server : ${speed.server}');
print('duration : ${speed.duration.inMilliseconds} ms');| Parameter | Default | Meaning |
|---|---|---|
downloadUrl / uploadUrl |
from config | Endpoints used |
timeout |
config.speedTestTimeout |
Per‑request timeout |
maxDuration |
config.speedTestMaxDuration |
Sampling window per phase |
uploadPayloadBytes |
config.uploadPayloadBytes |
Upload size |
pingHost / pingCount |
from config | Latency sampled during the test |
includeUpload |
true |
Skip the upload phase |
includePing |
true |
Skip the latency sample |
onProgress |
null |
Called repeatedly during both phases |
Progress phases: SpeedTestPhase.download → SpeedTestPhase.upload →
SpeedTestPhase.completed.
进度阶段依次为 download → upload → completed。
Fast, low‑traffic variant (skips upload and latency):
final quick = await NetworkDiagnostic.runSpeedTest(
includeUpload: false,
includePing: false,
maxDuration: const Duration(seconds: 5),
);final quality = await NetworkDiagnostic.evaluateQuality(
includePing: true,
includeDns: true,
includeSpeedTest: true,
includeUpload: true,
);
print('${quality.score.toStringAsFixed(1)}/100 — ${quality.level.label}');
quality.metrics.forEach((metric, value) {
print(' $metric = ${value.toStringAsFixed(2)}');
});
for (final suggestion in quality.suggestions) {
print('• $suggestion');
}| Parameter | Default | Meaning |
|---|---|---|
includePing |
true |
Sample latency / jitter / loss |
includeDns |
true |
Sample DNS latency |
includeSpeedTest |
true |
Sample download / upload (heavy) |
includeUpload |
true |
Include the upload metric |
pingHost / pingCount |
from config | Latency target |
dnsDomain / dnsServers |
from config | DNS target |
downloadUrl / uploadUrl |
from config | Bandwidth endpoints |
Lightweight variant (no bandwidth consumed):
final quality = await NetworkDiagnostic.evaluateQuality(includeSpeedTest: false);To change the ideal values used for scoring, apply them globally through the config — there is no per‑call target parameter:
ZeroNetworkKit.init(
config: const NetworkDiagnosticConfig(
qualityTargets: QualityTargets(excellentLatency: 20, acceptableLatency: 100),
),
);If you already have the metrics, skip the network entirely and score them with the pure function:
final score = NetworkQualityEvaluator.evaluate(
latency: 42,
jitter: 6,
packetLoss: 0,
download: 88.4,
upload: 12.1,
dns: 25,
signalStrength: -55,
targets: const QualityTargets(),
);
print('${score.score.toStringAsFixed(1)} → ${score.level.label}');
print(score.suggestions);Level thresholds: ≥90 excellent · ≥75 good · ≥60 fair · ≥40 poor ·
otherwise bad (NetworkQualityLevel.fromScore).
等级阈值:≥90 极佳 · ≥75 良好 · ≥60 一般 · ≥40 较差 · 其余为极差。
Weights applied to whatever metrics are available:
Metric key in quality.metrics |
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 of successful DnsTestResult.responseTimeMs |
signalStrength |
0.10 | NetworkConnectionInfo.signalStrength |
final report = await NetworkDiagnostic.diagnose(
includePing: true,
includeDns: true,
includePorts: true,
includeSpeedTest: true,
includeUpload: true,
host: '1.1.1.1',
dnsDomain: 'example.com',
dnsServers: const <String>['1.1.1.1', '8.8.8.8'],
ports: const <int>[80, 443],
);
print(report); // one-line summary
print(report.connection.type.label);
print(report.ping?.averageTime);
print(report.dnsResults.length);
print(report.portResults.where((p) => p.isOpen).length);
print(report.speedTest?.downloadSpeed);
print(report.quality.level.label);includePorts defaults to false, so a default run does not scan ports.
A failing sub‑test never aborts the run — its field is simply absent/null.
includePorts 默认 false;任何子项失败都不会中断整体流程,对应字段保持空。
final suite = await NetworkBenchmark.runAll(
iterations: 20,
warmupIterations: 3,
host: '1.1.1.1',
dnsDomain: 'example.com',
port: 443,
);
for (final result in suite.results) {
print('${result.testName.padRight(12)} '
'avg ${(result.averageDuration.inMicroseconds / 1000).toStringAsFixed(2)} ms '
'± ${(result.standardDeviation.inMicroseconds / 1000).toStringAsFixed(2)} ms '
'${result.operationsPerSecond.toStringAsFixed(1)} ops/s '
'failures=${result.failures}');
}
// Look up one benchmark by name:
print(suite['ping']?.averageDuration);
print(suite.totalDuration);Individual suites:
await NetworkBenchmark.benchmarkConnection(iterations: 20);
await NetworkBenchmark.benchmarkPing(iterations: 20, host: '1.1.1.1');
await NetworkBenchmark.benchmarkDns(iterations: 20, domain: 'example.com');
await NetworkBenchmark.benchmarkPortCheck(iterations: 20, port: 443);
await NetworkBenchmark.benchmarkPlatformChannel(iterations: 20);Use this to decide whether a diagnostic belongs on your startup path. 用它来判断某项诊断能否放在启动路径上。
final version = await ZeroNetworkKit.getPlatformVersion();
print(version); // e.g. 'Android 14' / 'iOS 18.0'
final details = await ZeroNetworkKit.getNativeNetworkDetails();
if (details != null) {
print(details); // SSID, BSSID, gateway, MAC, VPN flag, RSSI …
}The same methods exist on NetworkDiagnostic for convenience:
await NetworkDiagnostic.getPlatformVersion();
await NetworkDiagnostic.getNativeNetworkDetails();Both return null (or a partial map) when the platform has no such data or the
permission is missing — they never throw.
缺少权限或平台无此数据时返回 null,不会抛异常。
Every result object implements toMap() and is JSON encodable:
import 'dart:convert';
final report = await NetworkDiagnostic.diagnose(includeSpeedTest: false);
final json = jsonEncode(report.toMap());
print(json);
// Round-trip a connection snapshot.
final decoded = NetworkConnectionInfo.fromMap(jsonDecode(jsonEncode(
report.connection.toMap(),
)) as Map<Object?, Object?>);| Type | Serialisation |
|---|---|
NetworkConnectionInfo |
toMap() + fromMap() |
PingResult |
toMap() |
DnsTestResult |
toMap() |
PortCheckResult |
toMap() |
SpeedTestResult |
toMap() |
NetworkQualityScore |
toMap() |
NetworkDiagnosticReport |
toMap() |
BenchmarkResult / BenchmarkSuiteResult |
toMap() |
Every service accepts its collaborators, so your integration points are unit‑testable without touching the network. 每个服务都支持注入协作对象,可以完全脱离网络做单测。
class FakeConnectivityAdapter implements ConnectivityAdapter {
@override
Future<List<String>> checkConnectivity() async => <String>['wifi'];
@override
Stream<List<String>> get onConnectivityChanged => const Stream.empty();
}
NetworkDiagnostic.configure(
config: const NetworkDiagnosticConfig(pingHost: '127.0.0.1'),
connectivity: ConnectivityService(adapter: FakeConnectivityAdapter()),
ping: PingService(),
dns: DnsService(),
ports: PortService(),
speedTest: SpeedTestService(client: fakeClient),
);
// Restore defaults afterwards.
NetworkDiagnostic.reset();Swap individual services directly when you prefer:
NetworkDiagnostic.pingService = MyFakePingService();
NetworkDiagnostic.dnsService = MyFakeDnsService();| Member | Notes |
|---|---|
| values | none, wifi, mobile, ethernet, vpn, bluetooth, other |
label |
Human readable: 'Wi-Fi', 'Mobile', … |
id |
Stable English identifier (name), safe to persist |
isConnected |
true unless none |
NetworkType.fromRaw(Object?) |
Parses a raw platform string |
isConnected, type, ssid, signalStrength (dBm), ipAddress,
ipv6Address, gateway, macAddress, isVpn, isReachable, timestamp,
plus copyWith({bool? isReachable}).
Fields host, port, mode, sent, received, times, timestamp;
derived getters lost, packetLoss (%), minTime, maxTime, averageTime,
jitter, isSuccess.
Fields server, domain, isSuccess, responseTime, resolvedIps,
errorMessage, timestamp; derived responseTimeMs, primaryAddress.
Fields host, port, isOpen, responseTime, errorMessage, timestamp;
derived responseTimeMs.
Fields downloadSpeed, uploadSpeed (Mbps), ping (ms), jitter (ms),
packetLoss (%), downloadedBytes, uploadedBytes, downloadDuration,
uploadDuration, server, timestamp; derived duration; static helper
SpeedTestResult.mbpsFromBytes(bytes, elapsed).
phase (download, upload, completed), bytes, elapsed, speedMbps.
score (0–100), level, metrics (Map<String, double>), suggestions,
timestamp; NetworkQualityLevel.fromScore(double), level.label.
connection, ping, dnsResults, portResults, speedTest, quality,
timestamp.
BenchmarkResult: testName, iterations, totalDuration,
averageDuration, minDuration, maxDuration, standardDeviation,
operationsPerSecond, failures, timestamp.
BenchmarkSuiteResult: suiteName, results, totalDuration, timestamp,
and suite['name'].
Public constructor helpers: BenchmarkResult.fromSamples(...).
type.label, notdisplayName.NetworkTypeexposeslabel/id.checkPort()returnsbool. UsescanPorts(..., ports: [p])when you need RTT or the error message.includeSystemResolverdefaults tofalse. Passtrueto add thesystemDNS row.- A TCP ping needs a listening port. Probing
hostwithport: 443fails if that host does not accept TCP/443 — that is “filtered”, not “offline”. Usemode: PingMode.icmpon desktop to get closer to real ICMP. PingMode.icmpis desktop‑only and silently degrades when thepingbinary is unavailable.- The speed test moves real traffic (≈25 MB download by default). Ask for consent before running it on a metered connection.
- Missing permissions degrade, they never throw. A missing location
permission yields
ssid == null; the rest of the snapshot still arrives. includePortsisfalseby default indiagnose().init()is optional, butdispose()only closes the client the plugin owns — anhttpClientyou injected stays open, so close it yourself.