flutter_network_toolkit 1.0.0
flutter_network_toolkit: ^1.0.0 copied to clipboard
A comprehensive Flutter toolkit for real-time network connectivity monitoring, reliable HTTP client with automatic retry, ping diagnostics, and ready-to-use offline UI widgets.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_network_toolkit/flutter_network_toolkit.dart';
void main() {
runApp(const ToolkitExampleApp());
}
class ToolkitExampleApp extends StatelessWidget {
const ToolkitExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Network Toolkit Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
brightness: Brightness.light,
),
darkTheme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
brightness: Brightness.dark,
),
home: const OfflineBanner(
position: BannerPosition.top,
child: HomeScreen(),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final NetworkDiagnostics _diagnostics = NetworkDiagnostics();
late final NetworkClient _client;
DiagnosticReport? _latestReport;
bool _isDiagnosing = false;
String _apiResult = 'No request sent yet.';
bool _isLoadingApi = false;
@override
void initState() {
super.initState();
_client = NetworkClient(
baseUrl: Uri.parse('https://jsonplaceholder.typicode.com'),
interceptors: [NetworkLoggingInterceptor()],
retryPolicy: const RetryPolicy(
maxRetries: 3,
initialDelay: Duration(milliseconds: 500),
),
);
}
@override
void dispose() {
_client.dispose();
super.dispose();
}
Future<void> _runDiagnostics() async {
setState(() => _isDiagnosing = true);
try {
final report = await _diagnostics.runDiagnosticReport();
setState(() => _latestReport = report);
} finally {
if (mounted) setState(() => _isDiagnosing = false);
}
}
Future<void> _fetchData() async {
setState(() {
_isLoadingApi = true;
_apiResult = 'Sending request...';
});
try {
final response = await _client.get('/todos/1');
setState(() {
_apiResult = 'Status: ${response.statusCode}\n\n${response.data}';
});
} on NetworkException catch (e) {
setState(() {
_apiResult = 'Caught NetworkException:\n$e';
});
} catch (e) {
setState(() {
_apiResult = 'Unexpected error: $e';
});
} finally {
if (mounted) setState(() => _isLoadingApi = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Network Toolkit'),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Check Network',
onPressed: () => NetworkWatcher.instance.checkNetwork(),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Live status card
_buildStatusCard(),
const SizedBox(height: 16),
// Latency & Diagnostics
_buildDiagnosticsCard(),
const SizedBox(height: 16),
// Resilient HTTP Client card
_buildHttpClientCard(),
const SizedBox(height: 16),
// Full Screen Offline Preview
_buildOfflinePreviewCard(),
],
),
);
}
Widget _buildStatusCard() {
return NetworkStatusBuilder(
builder: (context, info, _) {
final isConnected = info.isConnected;
final color = isConnected ? Colors.green : Colors.red;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: color.withValues(alpha: 0.3), width: 1.5),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.15),
shape: BoxShape.circle,
),
child: Icon(
isConnected ? Icons.wifi : Icons.wifi_off,
color: color,
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isConnected ? 'Online & Connected' : 'Offline / No Access',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
'Quality: ${info.quality.name.toUpperCase()}',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
Chip(
label: Text(
info.status.name.toUpperCase(),
style: TextStyle(
color: color,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
backgroundColor: color.withValues(alpha: 0.1),
side: BorderSide.none,
),
],
),
const Divider(height: 24),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildInfoChip(
Icons.router,
'Types: ${info.connectionTypes.map((t) => t.name).join(", ")}',
),
_buildInfoChip(
Icons.speed,
info.latencyMs != null
? '${info.latencyMs} ms'
: 'Latency unmeasured',
),
_buildInfoChip(
Icons.cloud_done,
info.hasInternetAccess
? 'Internet Verified'
: 'No Internet WAN',
),
],
),
],
),
),
);
},
);
}
Widget _buildDiagnosticsCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Network Diagnostics',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
ElevatedButton.icon(
onPressed: _isDiagnosing ? null : _runDiagnostics,
icon: _isDiagnosing
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.play_arrow, size: 18),
label: const Text('Run Ping Test'),
),
],
),
const SizedBox(height: 12),
if (_latestReport != null) ...[
Text(
'Average: ${_latestReport!.averageLatencyMs}ms | Min: ${_latestReport!.minLatencyMs}ms | Max: ${_latestReport!.maxLatencyMs}ms',
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
'Jitter: ${_latestReport!.jitterMs}ms | Loss: ${(_latestReport!.packetLossRate * 100).toStringAsFixed(0)}% | Quality: ${_latestReport!.quality.name}',
style: Theme.of(context).textTheme.bodySmall,
),
] else
const Text(
'Tap "Run Ping Test" to measure latency and packet stability.',
style: TextStyle(color: Colors.grey),
),
],
),
),
);
}
Widget _buildHttpClientCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Resilient HTTP Client with Retry',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 6),
const Text(
'Sends request with automatic exponential backoff retries & interceptor logging.',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
const SizedBox(height: 12),
Row(
children: [
ElevatedButton.icon(
onPressed: _isLoadingApi ? null : _fetchData,
icon: _isLoadingApi
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.send, size: 18),
label: const Text('Fetch Todo Item'),
),
const SizedBox(width: 12),
ConnectivityAware(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Action allowed! Device is online.')),
);
},
child: OutlinedButton(
onPressed: null, // Handled by ConnectivityAware
child: const Text('Guarded Action'),
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: SelectableText(
_apiResult,
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
),
),
],
),
),
);
}
Widget _buildOfflinePreviewCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: ListTile(
leading: const Icon(Icons.screen_lock_portrait_outlined),
title: const Text('Preview "No Internet" Screen'),
subtitle: const Text('View the full-screen empty state widget'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (screenContext) => NoInternetScreen(
onRetry: () async {
await Future<void>.delayed(const Duration(seconds: 1));
if (screenContext.mounted) Navigator.pop(screenContext);
},
),
),
);
},
),
);
}
Widget _buildInfoChip(IconData icon, String text) {
return Chip(
avatar: Icon(icon, size: 16),
label: Text(text, style: const TextStyle(fontSize: 12)),
visualDensity: VisualDensity.compact,
);
}
}