flutter_ai_debugger 0.1.2
flutter_ai_debugger: ^0.1.2 copied to clipboard
AI-powered Flutter error tracker & debugger using Gemini: capture, analyze, store (Hive), and export (CSV/JSON).
flutter_ai_debugger #
AI-powered Flutter error tracker & debugger using Gemini: capture, analyze, store (Hive), and export (CSV/JSON).
Quick Start #
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_ai_debugger/flutter_ai_debugger.dart';
final GlobalKey<NavigatorState> appNavigatorKey = GlobalKey<NavigatorState>();
void main() {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
const envKey = String.fromEnvironment('GEMINI_API_KEY');
const fallback = '<PASTE_KEY>'; // for quick local try only
final apiKey = envKey.isEmpty ? fallback : envKey;
await AiDebugger.init(
AiDebugConfig(
apiKey: apiKey,
enableInternalLogs: true,
),
onNewReport: (r) {},
);
runApp(const MyApp());
}, (e, s) => AiDebugger.logError(e, s));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: appNavigatorKey,
builder: (context, child) {
return Stack(
children: [
if (child != null) child,
AiDebugDraggableButton(
navigatorKey: appNavigatorKey,
),
],
);
},
home: Scaffold(
appBar: AppBar(title: const Text('AI Debugger Example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
ElevatedButton(
onPressed: () => throw Exception('Forced error'),
child: const Text('Cause Error'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const AiDebugDashboard()),
),
child: const Text('Open AI Debug Dashboard (manual)'),
),
],
),
),
);
}
}
Passing the API key #
- Preferred:
flutter run --dart-define=GEMINI_API_KEY=YOUR_KEY
- For CI: use secrets
--dart-define=GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }}
Optional: export reports #
- Use
ExportService.exportJson(...)
orexportCsv(...)
. In tests, pass a customDirectory
.- Or let the package pick a user-visible path:
final jsonFile = await ExportService.exportJsonToPickedDir(AiDebugger.getReports()); final csvFile = await ExportService.exportCsvToPickedDir(AiDebugger.getReports());
Platform paths and sharing
- Android: uses app-specific external storage when available; generally visible in file managers.
- iOS: uses app Documents. To show in Files app, enable file sharing in
ios/Runner/Info.plist
:<key>UIFileSharingEnabled</key><true/> <key>LSSupportsOpeningDocumentsInPlace</key><true/>
- Desktop: prefers Downloads, falls back to Documents.
Share or open files right after export:
import 'package:share_plus/share_plus.dart';
import 'package:open_filex/open_filex.dart';
final file = await ExportService.exportCsvToPickedDir(AiDebugger.getReports());
await Share.shareXFiles([XFile(file.path)], text: 'AI error reports');
await OpenFilex.open(file.path);
Notes #
AiDebugDraggableButton
is visible only in debug by default. You can customizevisible
,onlyInDebug
,icon
,initialPosition
, andsize
.- The dashboard updates live via Hive listenable.
Capturing API exceptions #
- Unhandled async exceptions are captured automatically if your app runs inside a guarded zone (as shown in Quick Start). Ensure network errors throw.
- If you catch errors yourself (e.g., in repositories or interceptors), call
AiDebugger.logError(e, s)
so a report is still generated.
Manual try/catch example:
try {
final res = await http.get(Uri.parse('https://api.example.com'));
if (res.statusCode >= 400) {
throw Exception('Request failed: ${res.statusCode}');
}
} catch (e, s) {
await AiDebugger.logError(e, s);
}
Dio interceptor example:
dio.interceptors.add(
InterceptorsWrapper(
onError: (e, handler) async {
await AiDebugger.logError(e, e.stackTrace);
handler.next(e);
},
),
);
Testing API error capture #
Inject a fake Gemini service and a temp Hive path, then throw an async error inside a guarded zone:
import 'dart:async';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_ai_debugger/flutter_ai_debugger.dart';
import 'package:flutter_gemini/flutter_gemini.dart';
import 'package:hive/hive.dart';
class FakeGeminiService extends GeminiService {
@override
Future<AiErrorReport> analyze({
required String id,
required String error,
required String stack,
String? modelName,
List<SafetySetting>? safetySettings,
GenerationConfig? generationConfig,
}) async {
return AiErrorReport(
id: id,
originalError: error,
stackTrace: stack,
explanation: 'api error captured',
possibleCauses: const [],
fixes: const [],
createdAt: DateTime.now(),
);
}
}
void main() {
test('captures unhandled API exception via zone', () async {
final temp = await Directory.systemTemp.createTemp();
Hive.init(temp.path);
Hive.registerAdapter(AiErrorReportAdapter());
await Hive.openBox<AiErrorReport>('ai_error_reports');
await AiDebugger.init(
const AiDebugConfig(apiKey: 'test'),
service: FakeGeminiService(),
hive: Hive,
hivePath: temp.path,
);
final done = Completer<void>();
runZonedGuarded(() async {
Future<void>.delayed(const Duration(milliseconds: 10), () {
throw Exception('API failed');
});
await Future.delayed(const Duration(milliseconds: 50));
done.complete();
}, (e, s) => AiDebugger.logError(e, s));
await done.future;
final last = AiDebugger.getLastReport();
expect(last, isNotNull);
expect(last!.explanation, 'api error captured');
});
}