beekon_flutter 0.0.1
beekon_flutter: ^0.0.1 copied to clipboard
Flutter plugin for the Beekon location SDK (Android + iOS).
example/lib/main.dart
import 'package:beekon_flutter/beekon_flutter.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const BeekonExampleApp());
}
class BeekonExampleApp extends StatelessWidget {
const BeekonExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Beekon Flutter Example',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.amber),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<Position> _liveFeed = <Position>[];
String? _error;
@override
void initState() {
super.initState();
_bootstrap();
Beekon.instance.positions.listen((Position p) {
setState(() {
_liveFeed.insert(0, p);
if (_liveFeed.length > 100) _liveFeed.removeLast();
});
});
}
Future<void> _bootstrap() async {
try {
await Beekon.instance.initialize();
await Beekon.instance.configure(
const BeekonConfig(
preset: BeekonPreset.balanced,
androidNotification: AndroidNotification(
channelId: 'beekon-tracking',
channelName: 'Tracking',
notificationId: 7411,
title: 'Beekon',
text: 'Tracking your location',
// mipmap/ic_launcher always exists in a flutter create scaffold.
smallIcon: 'ic_launcher',
),
),
);
} catch (e) {
setState(() => _error = e.toString());
}
}
Future<void> _toggle(BeekonState current) async {
try {
if (current is Tracking || current is Starting) {
await Beekon.instance.stop();
} else {
await Beekon.instance.start();
}
} catch (e) {
setState(() => _error = e.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Beekon Flutter Example'),
actions: <Widget>[
IconButton(
tooltip: 'History',
icon: const Icon(Icons.history),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext _) => const HistoryPage(),
),
),
),
],
),
body: StreamBuilder<BeekonState>(
stream: Beekon.instance.state,
initialData: const Idle(),
builder: (BuildContext context, AsyncSnapshot<BeekonState> snap) {
final BeekonState state = snap.data ?? const Idle();
return Column(
children: <Widget>[
_StatusCard(state: state, error: _error),
Padding(
padding: const EdgeInsets.all(16),
child: FilledButton.icon(
icon: Icon(_isActive(state) ? Icons.stop : Icons.play_arrow),
label: Text(_isActive(state) ? 'Stop' : 'Start'),
onPressed: () => _toggle(state),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text('Live positions'),
Text('${_liveFeed.length} buffered',
style: Theme.of(context).textTheme.bodySmall),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _liveFeed.length,
itemBuilder: (BuildContext context, int i) {
final Position p = _liveFeed[i];
return _PositionTile(p: p);
},
),
),
],
);
},
),
);
}
bool _isActive(BeekonState s) => s is Tracking || s is Starting;
}
class _StatusCard extends StatelessWidget {
const _StatusCard({required this.state, required this.error});
final BeekonState state;
final String? error;
@override
Widget build(BuildContext context) {
final ThemeData t = Theme.of(context);
final String label = switch (state) {
Idle() => 'Idle',
Starting() => 'Starting…',
Tracking() => 'Tracking',
Paused(reason: final PauseReason r) => 'Paused (${r.name})',
Stopped() => 'Stopped',
};
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('State', style: t.textTheme.bodySmall),
const SizedBox(height: 4),
Text(label, style: t.textTheme.headlineSmall),
if (error != null) ...<Widget>[
const SizedBox(height: 8),
Text(error!,
style: t.textTheme.bodySmall?.copyWith(
color: t.colorScheme.error)),
],
],
),
),
);
}
}
class _PositionTile extends StatelessWidget {
const _PositionTile({required this.p});
final Position p;
@override
Widget build(BuildContext context) {
return ListTile(
dense: true,
leading: const Icon(Icons.place_outlined, size: 20),
title: Text('${p.lat.toStringAsFixed(5)}, ${p.lng.toStringAsFixed(5)}'),
subtitle: Text(
'±${p.accuracy.toStringAsFixed(1)} m • '
'${p.speed.toStringAsFixed(1)} m/s • '
'${p.timestamp.toLocal()}',
),
);
}
}
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
State<HistoryPage> createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
late DateTime _from;
late DateTime _to;
Future<List<Position>>? _future;
@override
void initState() {
super.initState();
_to = DateTime.now();
_from = _to.subtract(const Duration(hours: 24));
}
Future<void> _pickRange() async {
final DateTimeRange? range = await showDateRangePicker(
context: context,
firstDate: DateTime(2025),
lastDate: DateTime.now(),
initialDateRange: DateTimeRange(start: _from, end: _to),
);
if (range != null) {
setState(() {
_from = range.start;
_to = range.end;
_future = Beekon.instance.history(from: _from, to: _to);
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('History'),
actions: <Widget>[
IconButton(
tooltip: 'Pick date range',
icon: const Icon(Icons.date_range),
onPressed: _pickRange,
),
],
),
body: FutureBuilder<List<Position>>(
future: _future ??= Beekon.instance.history(from: _from, to: _to),
builder:
(BuildContext context, AsyncSnapshot<List<Position>> snap) {
if (snap.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snap.hasError) {
return Center(child: Text('Error: ${snap.error}'));
}
final List<Position> rows = snap.data ?? <Position>[];
if (rows.isEmpty) {
return const Center(child: Text('No history in this range.'));
}
return ListView.builder(
itemCount: rows.length,
itemBuilder: (BuildContext context, int i) =>
_PositionTile(p: rows[i]),
);
},
),
);
}
}