vk_location_sharing
A powerful and flexible Flutter package for implementing real-time location sharing with both foreground streams and periodic background updates. It provides a clean, unified API to handle permissions and location tracking with ease.
โจ Features
- โ
Single Initialize Function: Call
initialize()once in your main.dart to set up everything - ๐ฏ Unified Location Sharing: Simple
shareLocation()method that handles all permissions and starts tracking - ๐ Real-time Foreground Stream: Live location updates as the user moves
- ๐ Periodic Background Updates: Regular location tracking even when app is backgrounded (via Workmanager)
- ๐ Flexible Stream Handling: Choose to handle foreground and background updates separately or combined
- ๐๏ธ Custom Callbacks: Execute custom code with each location update (e.g., send to backend)
- ๐ Built-in Permission Handling: Automatic permission request and verification
- โ๏ธ Highly Configurable: Customize accuracy, distance filters, update frequency, and more
- ๐ฑ Battery Optimized: Uses distance filters to minimize battery drain
- ๐งช Well Tested: Comprehensive unit tests with mocking support
๐ฆ Installation
Add vk_location_sharing to your pubspec.yaml:
dependencies:
vk_location_sharing: ^1.0.2
Then run:
flutter pub get
๐ง Platform Setup
iOS
Add the following keys to your Info.plist file (or use Xcode UI):
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location to share it with others.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to your location at all times to share it with others.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to your location at all times to share it with others.</string>
For background location tracking, add to Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>fetch</string>
</array>
Android
โ ๏ธ CRITICAL: Background location setup is complex on Android. Follow ALL steps below.
1. Add Required Permissions to android/app/src/main/AndroidManifest.xml:
<!-- Foreground location permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- REQUIRED: Background location permission (most restrictive) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- REQUIRED for Android 12+: Foreground service permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Recommended: For sending locations to backend -->
<uses-permission android:name="android.permission.INTERNET" />
2. Update android/app/build.gradle:
android {
compileSdkVersion 34 // โ ๏ธ Must be 33+ for Android 12+ support
targetSdkVersion 34 // โ ๏ธ Set to 33+ for foreground service support
defaultConfig {
minSdkVersion 21 // Workmanager requires API 21+
}
}
3. CRITICAL - Runtime Permissions:
The ACCESS_BACKGROUND_LOCATION permission must be requested separately:
// First, request standard location permission
final permission = await Permission.location.request();
// Then, request background location permission
if (permission.isGranted) {
final bgPermission = await Permission.locationAlways.request();
// bgPermission will be granted if user allows "Always" access
}
Note: Users must grant "Allow all the time" (not "Allow only while using the app") for background location to work. This is an OS-level requirement on Android 10+.
4. Test on Physical Device:
- โ ๏ธ Background location does NOT work on Android emulator
- Must test on a real Android device with location services enabled
- Ensure battery optimization is disabled for your app in Settings
5. Troubleshooting Android Background Location:
Problem: Background location updates not being received
-
Check that user has granted "Always allow" permission:
adb shell dumpsys package com.yourapp.name | grep android.permission.ACCESS_BACKGROUND_LOCATION -
Verify app is not restricted by battery optimization:
adb shell dumpsys deviceidle whitelist -
Check if Workmanager is scheduling tasks (debug logs):
adb logcat | grep "BG_SERVICE" -
Ensure device has GPS signal (some indoor locations won't work)
โ ๏ธ Compatibility
- Flutter: 3.10.0 or higher
- Dart: 3.1.0 or higher
- Android: API 21+ (Workmanager requirement), compileSdk 33+ (Android 12+ foreground service)
- iOS: 11.0+
- Physical Device: Required for Android background location testing (not supported on emulator)
๐ Quick Start
Basic Usage
import 'package:flutter/material.dart';
import 'package:vk_location_sharing/vk_location_sharing.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _vkLocation = VkLocationSharing();
Position? _currentPosition;
bool _isSharing = false;
@override
void initState() {
super.initState();
_initializeLocation();
}
Future<void> _initializeLocation() async {
// Initialize the package once in your app
await _vkLocation.initialize(
config: LocationConfig(
accuracy: LocationAccuracy.best,
distanceFilter: 5, // Update every 5 meters
backgroundUpdateFrequency: const Duration(minutes: 15),
),
);
// Listen to location updates
_vkLocation.foregroundLocationStream.listen((position) {
setState(() {
_currentPosition = position;
});
});
}
Future<void> _toggleSharing() async {
if (!_isSharing) {
final success = await _vkLocation.shareLocation();
if (success) {
setState(() => _isSharing = true);
}
} else {
await _vkLocation.stopSharing();
setState(() => _isSharing = false);
}
}
@override
void dispose() {
_vkLocation.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Location Sharing')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_currentPosition != null)
Text('Lat: ${_currentPosition!.latitude}\nLng: ${_currentPosition!.longitude}')
else
const Text('No location data'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleSharing,
child: Text(_isSharing ? 'Stop Sharing' : 'Start Sharing'),
),
],
),
),
);
}
}
๐ Advanced Usage
Separate Foreground and Background Streams
await _vkLocation.initialize(
config: LocationConfig(
handleForegroundAndBackgroundSeparately: true,
accuracy: LocationAccuracy.best,
distanceFilter: 5,
backgroundUpdateFrequency: const Duration(minutes: 15),
),
);
// Listen to foreground updates
_vkLocation.foregroundLocationStream.listen((position) {
print('Foreground: ${position.latitude}');
});
// Listen to background updates separately
_vkLocation.backgroundLocationStream.listen((position) {
print('Background: ${position.latitude}');
});
Custom Location Handler
await _vkLocation.initialize(
config: LocationConfig(
accuracy: LocationAccuracy.best,
onLocationUpdate: (position, {required isBackground}) async {
// Send to your backend API
if (isBackground) {
await sendLocationToBackend(position, background: true);
} else {
await sendLocationToBackend(position, background: false);
}
},
),
);
Selective Sharing
// Start only foreground sharing
await _vkLocation.shareLocation(includeForeground: true, includeBackground: false);
// Start only background sharing
await _vkLocation.shareLocation(includeForeground: false, includeBackground: true);
// Stop only background sharing
await _vkLocation.stopSharing(stopForeground: false, stopBackground: true);
๐ง Debugging Background Location
Enable debug logging to diagnose background location issues:
// During initialization, set a detailed onLocationUpdate callback:
await _vkLocation.initialize(
config: LocationConfig(
accuracy: LocationAccuracy.best,
backgroundUpdateFrequency: const Duration(minutes: 5), // Use shorter interval for testing
onLocationUpdate: (position, {required isBackground}) async {
final debugInfo = '''
Location Update:
- Background: $isBackground
- Lat: ${position.latitude}
- Lng: ${position.longitude}
- Accuracy: ${position.accuracy}m
- Timestamp: ${DateTime.now()}
''';
print(debugInfo);
// Send to backend or log for analysis
// await sendLocationToBackend(position, isBackground);
},
),
);
Debug Output Examples:
โ Foreground working:
[BG_SERVICE] Background task triggered
I/flutter: Location Update:
- Background: false
- Lat: 37.7749
- Lng: -122.4194
- Accuracy: 10m
โ Background not working:
- No
[BG_SERVICE]messages appear in logs - Solution: Check Android permissions and background limitations
โ Callback not called:
- Task triggers but callback doesn't execute
- Solution: Ensure
onLocationUpdatecallback is properly registered duringinitialize()
๐ฏ Configuration Options
The LocationConfig class provides these customization options:
| Parameter | Type | Default | Description |
|---|---|---|---|
handleForegroundAndBackgroundSeparately |
bool | false | If true, use separate streams for foreground/background. If false, all updates go to foreground stream. |
accuracy |
LocationAccuracy | high | Desired GPS accuracy (high, best, low, lowest) |
distanceFilter |
int | 0 | Minimum distance in meters between updates |
backgroundUpdateFrequency |
Duration | 15 minutes | Frequency of background updates |
onLocationUpdate |
Function | null | Custom callback executed with each location update |
๐ก API Reference
VkLocationSharing
initialize({required LocationConfig config})
Initializes the package with configuration. Must be called before shareLocation().
shareLocation({bool includeForeground = true, bool includeBackground = true})
Starts location sharing after requesting necessary permissions.
- Returns:
bool- true if permissions granted and tracking started, false otherwise - Parameters:
includeForeground: Enable foreground location trackingincludeBackground: Enable background location tracking
stopSharing({bool stopForeground = true, bool stopBackground = true})
Stops location sharing.
foregroundLocationStream
Stream of foreground location updates.
backgroundLocationStream
Stream of background location updates (if handleForegroundAndBackgroundSeparately is true).
getCurrentLocation()
Fetch current location once.
dispose()
Clean up resources and close all streams.
โ ๏ธ Important Notes
Background Update Frequency: Background updates are handled via
Workmanager. The minimum frequency on most platforms is 15 minutes. This is a platform limitation, not a package limitation.
Battery Usage: Use the
distanceFilterparameter to reduce update frequency and save battery. For example, set it to 10-50 meters for reasonable accuracy with minimal battery drain.
Permission Handling: Always handle cases where permissions are denied. The
shareLocation()method returns false if permissions are not granted.
Cleanup: Always call
dispose()in your widget's dispose method to prevent memory leaks.
๐ Troubleshooting
Background location not updating
- Ensure
ACCESS_BACKGROUND_LOCATIONpermission is granted on Android - Check that background modes are enabled in iOS Info.plist
- Verify workmanager is initialized properly during
initialize()call
Foreground stream not emitting
- Ensure location services are enabled on the device
- Check that foreground location permission is granted
- Verify you called
initialize()beforeshareLocation()
App crashes on background updates
- Ensure all plugins used in the callback are background-safe
- Use
kDebugModechecks for debug logging to avoid issues
๐ License
MIT License - see LICENSE file for details.
๐ฅ Contributing
Contributions are welcome! Please feel free to submit a pull request.
๐ Support
For issues, questions, or suggestions, please visit the GitHub repository.