flutter_offline_background_location 1.0.0 copy "flutter_offline_background_location: ^1.0.0" to clipboard
flutter_offline_background_location: ^1.0.0 copied to clipboard

Offline background location tracking plugin. Records location when app is closed or without internet.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_offline_background_location/flutter_background_location.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Background Location Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const PermissionWrapper(child: HomeScreen()),
    );
  }
}

class PermissionWrapper extends StatefulWidget {
  const PermissionWrapper({super.key, required this.child});

  final Widget child;

  @override
  State<PermissionWrapper> createState() => _PermissionWrapperState();
}

class _PermissionWrapperState extends State<PermissionWrapper> {
  bool _checked = false;

  @override
  void initState() {
    super.initState();
    _requestPermissions();
  }

  Future<void> _requestPermissions() async {
    await Permission.locationWhenInUse.request();
    await Permission.locationAlways.request();
    if (await Permission.notification.isDenied) {
      await Permission.notification.request();
    }
    if (mounted) setState(() => _checked = true);
  }

  @override
  Widget build(BuildContext context) {
    if (!_checked) {
      return const Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CircularProgressIndicator(),
              SizedBox(height: 16),
              Text('Requesting permissions…'),
            ],
          ),
        ),
      );
    }
    return widget.child;
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _isLoading = false;
  String? _error;
  bool _tracking = false;
  final _plugin = FlutterBackgroundLocation.instance;

  Future<void> _startTracking() async {
    setState(() {
      _error = null;
      _isLoading = true;
    });
    try {
      await _plugin.configure(const BackgroundLocationConfig(
        intervalMinutes: 1,
        accuracy: LocationAccuracy.high,
        notificationTitle: 'Location tracking',
        notificationBody: 'Recording your position.',
        retentionDays: 30,
        maxRecords: 10000,
      ));
      await _plugin.initialize();
      await _plugin.startTracking();
      if (mounted) {
        setState(() {
          _isLoading = false;
          _error = null;
          _tracking = true;
        });
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Tracking started.')),
        );
      }
    } catch (e) {
      if (mounted) {
        setState(() {
          _isLoading = false;
          _error = e.toString();
        });
      }
    }
  }

  Future<void> _stopTracking() async {
    setState(() {
      _error = null;
      _isLoading = true;
    });
    try {
      await _plugin.stopTracking();
      if (mounted) {
        setState(() {
          _isLoading = false;
          _tracking = false;
        });
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Tracking stopped.')),
        );
      }
    } catch (e) {
      if (mounted) {
        setState(() {
          _isLoading = false;
          _error = e.toString();
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Background Location'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            if (_error != null) ...[
              Card(
                color: Theme.of(context).colorScheme.errorContainer,
                child: Padding(
                  padding: const EdgeInsets.all(12.0),
                  child: Text(_error!, style: const TextStyle(color: Colors.black87)),
                ),
              ),
              const SizedBox(height: 16),
            ],
            if (!_tracking)
              ElevatedButton.icon(
                onPressed: _isLoading ? null : _startTracking,
                icon: const Icon(Icons.play_arrow),
                label: const Text('Start tracking'),
                style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
              ),
            if (_tracking)
              ElevatedButton.icon(
                onPressed: _isLoading ? null : _stopTracking,
                icon: const Icon(Icons.stop),
                label: const Text('Stop tracking'),
                style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
              ),
            const SizedBox(height: 12),
            OutlinedButton.icon(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute<void>(
                    builder: (context) => const LocationHistoryScreen(),
                  ),
                );
              },
              icon: const Icon(Icons.history),
              label: const Text('Location history'),
              style: OutlinedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16)),
            ),
            if (_isLoading)
              const Padding(
                padding: EdgeInsets.only(top: 24),
                child: Center(child: CircularProgressIndicator()),
              ),
          ],
        ),
      ),
    );
  }
}

class LocationHistoryScreen extends StatefulWidget {
  const LocationHistoryScreen({super.key});

  @override
  State<LocationHistoryScreen> createState() => _LocationHistoryScreenState();
}

class _LocationHistoryScreenState extends State<LocationHistoryScreen> {
  List<LocationRecord> _locations = [];
  bool _loading = true;
  String? _error;
  final _plugin = FlutterBackgroundLocation.instance;

  Future<void> _load() async {
    setState(() {
      _loading = true;
      _error = null;
    });
    try {
      final list = await _plugin.getLocations(limit: 200);
      if (mounted) {
        setState(() {
          _locations = list;
          _loading = false;
          _error = null;
        });
      }
    } catch (e) {
      if (mounted) {
        setState(() {
          _loading = false;
          _error = e.toString();
        });
      }
    }
  }

  @override
  void initState() {
    super.initState();
    _load();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Location history'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _loading ? null : _load,
          ),
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_loading) {
      return const Center(child: CircularProgressIndicator());
    }
    if (_error != null) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(_error!, textAlign: TextAlign.center),
              const SizedBox(height: 16),
              ElevatedButton(onPressed: _load, child: const Text('Retry')),
            ],
          ),
        ),
      );
    }
    if (_locations.isEmpty) {
      return const Center(
        child: Text('No locations yet. Start tracking to record.'),
      );
    }
    return RefreshIndicator(
      onRefresh: _load,
      child: ListView.builder(
        padding: const EdgeInsets.symmetric(vertical: 8),
        itemCount: _locations.length,
        itemBuilder: (context, index) {
          final loc = _locations[index];
          return Card(
            margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
            child: ListTile(
              title: Text(
                '${loc.latitude.toStringAsFixed(6)}, ${loc.longitude.toStringAsFixed(6)}',
                style: const TextStyle(fontFamily: 'monospace'),
              ),
              subtitle: Text(
                _formatTimestamp(loc.timestamp),
                style: Theme.of(context).textTheme.bodySmall,
              ),
            ),
          );
        },
      ),
    );
  }

  String _formatTimestamp(int ms) {
    final dt = DateTime.fromMillisecondsSinceEpoch(ms);
    return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
        '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}';
  }
}
1
likes
150
points
41
downloads

Publisher

unverified uploader

Weekly Downloads

Offline background location tracking plugin. Records location when app is closed or without internet.

Homepage

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, path_provider, sqflite

More

Packages that depend on flutter_offline_background_location

Packages that implement flutter_offline_background_location