geohawk 0.1.5
geohawk: ^0.1.5 copied to clipboard
Resilient Android background location tracking — foreground service, watchdog/boot restart, offline cache, and OEM auto-start helpers.
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geohawk/geohawk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _geohawk = Geohawk.instance;
bool _running = false;
int _pending = 0;
LocationFix? _last;
StreamSubscription<LocationFix>? _sub;
final _titleCtrl = TextEditingController(text: 'Geohawk demo');
final _textCtrl = TextEditingController(text: 'Tracking your location');
@override
void initState() {
super.initState();
_refresh();
_sub = _geohawk.onLocationUpdate.listen((fix) {
setState(() => _last = fix);
});
}
@override
void dispose() {
_sub?.cancel();
_titleCtrl.dispose();
_textCtrl.dispose();
super.dispose();
}
Future<void> _refresh() async {
final running = await _geohawk.isRunning();
final pending = await _geohawk.pendingCacheCount();
if (!mounted) return;
setState(() {
_running = running;
_pending = pending;
});
}
Future<void> _start() async {
await _geohawk.start(
interval: const Duration(seconds: 30),
maxInterval: const Duration(seconds: 60),
distanceFilterMeters: 0,
sendUrl: 'https://example.com/track',
tokenUrl: 'https://example.com/auth/token',
id: 'demo-user-1',
payload: const {'app': 'geohawk-demo', 'version': 1},
notificationTitle: _titleCtrl.text,
notificationText: _textCtrl.text,
);
await _refresh();
}
Future<void> _updateNotification() async {
await _geohawk.updateNotification(
title: _titleCtrl.text,
text: _textCtrl.text,
);
}
Future<void> _stop() async {
await _geohawk.stop();
await _refresh();
}
Future<void> _syncNow() async {
final ok = await _geohawk.syncNow(
id: 'demo-manual',
payload: const {'trigger': 'user-tap'},
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(ok ? 'Sync sent' : 'Sync failed')),
);
}
@override
Widget build(BuildContext context) {
final last = _last;
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Geohawk example')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Running: $_running'),
Text('Pending uploads: $_pending'),
const SizedBox(height: 8),
if (last != null)
Text(
'Last fix: ${last.latitude.toStringAsFixed(5)}, '
'${last.longitude.toStringAsFixed(5)} '
'(±${last.accuracy.toStringAsFixed(1)}m)',
)
else
const Text('No fix yet'),
const SizedBox(height: 16),
TextField(
controller: _titleCtrl,
decoration: const InputDecoration(
labelText: 'Notification title',
),
),
TextField(
controller: _textCtrl,
decoration: const InputDecoration(
labelText: 'Notification message',
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
children: [
ElevatedButton(onPressed: _start, child: const Text('Start')),
ElevatedButton(onPressed: _stop, child: const Text('Stop')),
ElevatedButton(
onPressed: _syncNow,
child: const Text('Sync now'),
),
ElevatedButton(
onPressed: _updateNotification,
child: const Text('Update notification'),
),
ElevatedButton(
onPressed: _refresh,
child: const Text('Refresh'),
),
ElevatedButton(
onPressed: _geohawk.requestBatteryOptimizationExemption,
child: const Text('Battery exempt'),
),
ElevatedButton(
onPressed: _geohawk.openAutoStartSettings,
child: const Text('Auto-start'),
),
],
),
],
),
),
),
);
}
}