flutter_app_doctor 0.1.0
flutter_app_doctor: ^0.1.0 copied to clipboard
Runtime health checks for Flutter apps. Diagnoses connectivity, DNS, captive portals, API reachability, latency, storage, permissions, emulators, build mode, jank and clock skew into one structured, s [...]
import 'package:flutter/material.dart';
import 'package:flutter_app_doctor/flutter_app_doctor.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Frame timings only exist while the engine is drawing, so the collector has
// to be running before a diagnostic asks about them.
FrameStatsCollector.instance.start();
// Configure once at startup; every later FlutterDoctor.run() picks this up.
FlutterDoctor.configure(
DoctorConfig(
endpoints: <ApiEndpoint>[
ApiEndpoint(
Uri.parse('https://pub.flutter-io.cn/api/packages/http'),
name: 'pub.flutter-io.cn API',
),
ApiEndpoint(
// Deliberately broken, to show how a failure reads.
Uri.parse('https://api.this-host-does-not-exist.invalid/health'),
name: 'Missing backend',
isCritical: false,
),
],
appInfoProvider: const StaticAppInfoProvider(
AppInfo(
appName: 'flutter_app_doctor example',
version: '1.0.0',
buildNumber: '1',
packageName: 'dev.example.flutter_app_doctor_example',
),
),
permissionProvider: CallbackPermissionProvider(() async {
// Real apps would query permission_handler here.
return const <PermissionReport>[
PermissionReport(name: 'notifications', state: PermissionState.granted),
PermissionReport(
name: 'camera',
state: PermissionState.denied,
isRequired: false,
rationale: 'Used for scanning receipts.',
),
];
}),
),
);
runApp(const ExampleApp());
}
/// Demo host for the diagnostics screen.
class ExampleApp extends StatelessWidget {
/// Creates the demo app.
const ExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'flutter_app_doctor',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0553B1)),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF0553B1),
brightness: Brightness.dark,
),
),
home: const HomePage(),
);
}
/// Landing screen with the two ways to consume a report.
class HomePage extends StatefulWidget {
/// Creates the landing screen.
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String? _console;
bool _running = false;
Future<void> _printToConsole() async {
setState(() => _running = true);
final DoctorReport report = await FlutterDoctor.run();
debugPrint(report.summary);
if (!mounted) return;
setState(() {
_running = false;
_console = report.summary;
});
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('flutter_app_doctor')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
FilledButton.icon(
onPressed: _running ? null : _printToConsole,
icon: const Icon(Icons.terminal),
label: const Text('Run and print the summary'),
),
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const FlutterDoctorScreen(),
),
),
icon: const Icon(Icons.health_and_safety_outlined),
label: const Text('Open the diagnostics screen'),
),
const SizedBox(height: 16),
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
child: SelectableText(
_console ??
'Run a diagnostic to see the plain-text report '
'here.\n\nThe "Missing backend" endpoint is '
'intentionally unreachable so you can see how a '
'failure and its remedy are rendered.',
style: const TextStyle(
fontFamily: 'monospace',
fontFamilyFallback: <String>['Menlo', 'Courier New'],
fontSize: 12,
),
),
),
),
),
),
),
],
),
),
);
}