Quickstart
This assumes the package is already installed (see
Installation) and that your
app’s manifest/Info.plist already declare the permissions covered in the
previous page, Permissions & background location —
the SDK cannot request a permission the app hasn’t declared.
A complete main.dart
import 'package:bgeo_background_geolocation/bgeo_background_geolocation.dart' as bg;import 'package:flutter/material.dart';
void main() { runApp(const MyApp());}
class MyApp extends StatelessWidget { const MyApp({super.key});
@override Widget build(BuildContext context) { return const MaterialApp(home: HomePage()); }}
class HomePage extends StatefulWidget { const HomePage({super.key});
@override State<HomePage> createState() => _HomePageState();}
class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); _init(); }
Future<void> _init() async { // Subscribe BEFORE ready() so no early location/motionchange event — // including the initial `isMoving` probe ready() may fire while // resuming an already-enabled session — is missed. bg.BackgroundGeolocation.onLocation((location) { debugPrint('[location] ${location.coords} moving: ${location.isMoving}'); });
bg.BackgroundGeolocation.onMotionChange((event) { debugPrint('[motionchange] ${event.isMoving} ${event.location?.coords}'); });
final state = await bg.BackgroundGeolocation.ready(bg.Config( desiredAccuracy: bg.desiredAccuracyHigh, distanceFilter: 30, // metres between points while moving // stopTimeout is in MINUTES, not seconds (see the Config reference) — // 5 minutes of continuous stillness before the engine commits to // stationary. This is also the SDK default; spelled out here for clarity. stopTimeout: 5, url: 'https://your-server.example/locations', logLevel: bg.logLevelInfo, stopOnTerminate: false, // keep tracking after the app process is killed startOnBoot: true, // resume tracking after a device reboot ));
if (!state.enabled) { // requestPermission() runs the staged permission flow (Android: // fine -> background -> activity recognition; iOS: location // authorization) — see the Permissions page for what each platform // needs declared before this resolves with anything but denied. final status = await bg.BackgroundGeolocation.requestPermission(); if (status == bg.authorizationStatusAlways) { await bg.BackgroundGeolocation.start(); } else { // Denied, restricted, or When-In-Use-only — don't start tracking. // Prompt the user back through your own onboarding/Settings nudge. debugPrint('[bgeo] insufficient location authorization (status: $status) — not starting'); } } }
@override void dispose() { bg.BackgroundGeolocation.removeListeners(); super.dispose(); }
@override Widget build(BuildContext context) { return const Scaffold( body: Center(child: Text('BGeo is tracking in the background.')), ); }}What you should see
- Permission prompts. On first launch,
requestPermission()walks through the OS dialogs — Android’s staged “While using the app” → “Allow all the time” → activity-recognition prompts, or iOS’s When In Use / Always dialog. See Permissions & background location for the manifest/Info.plistdeclarations these prompts depend on — without them, the OS denies the request outright rather than showing a dialog. If the resolved status isn’tauthorizationStatusAlways, the code above logs a warning and skipsstart()instead of tracking with insufficient authorization. - A
motionchangeevent. Once tracking starts, walk (or drive) away from where the device was resting. You’ll see[motionchange] truelogged as the motion state machine decides the device is moving; standing still again for the configuredstopTimeoutlogs[motionchange] falseonce it commits back to stationary. locationevents. While moving,[location]logs fire roughly everydistanceFiltermetres, each withlocation.coordsand the currentisMovingverdict.- Queue draining, or 401s. Locations upload natively as they’re queued.
Point
urlat a real endpoint and you’ll see them arrive server-side with no Dart involvement; point it at a placeholder (as above) and you’ll instead see repeated connection failures/401s in the log as the queue keeps retrying — that’s expected until a real server is listening. See the HTTP guide for batching, retries, and JWT-refresh once you’re pointing at a real backend.
The same lines land in your terminal via flutter run; the example app’s
on-screen log (see the example app guide)
shows them on the device itself, interleaved with the native engine’s own
rows — see the logging guide.