Geofencing is a powerful feature in mobile app development that allows you to define virtual boundaries (geofences) and trigger actions when a device enters or exits these boundaries. In Flutter, you can implement geofencing to create location-aware applications that respond to user movements. This comprehensive guide will walk you through the process of implementing geofencing in Flutter, complete with code examples.
What is Geofencing?
Geofencing is the creation of a virtual perimeter around a real-world geographic area. By setting up a geofence, your app can receive notifications when a device crosses its boundary—either entering (dwell transition) or exiting (exit transition). This technology is used in various applications, such as:
- Location-based reminders
- Proximity marketing
- Security alerts
- Automated check-ins
Why Use Geofencing in Flutter?
- Engage Users: Deliver location-relevant content or promotions.
- Improve User Experience: Provide contextual interactions based on their physical location.
- Increase App Utility: Automate tasks and provide timely notifications.
Implementing Geofencing in Flutter: A Step-by-Step Guide
To implement geofencing in Flutter, you will need to use plugins that provide access to native platform APIs for geofencing. We’ll be using the geofence
plugin for this example.
Step 1: Set Up a New Flutter Project
First, create a new Flutter project if you don’t have one already:
flutter create geofence_app
Step 2: Add Dependencies
Add the geofence
plugin to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
geofence: ^0.2.1+2 # Use the latest version
Run flutter pub get
to install the dependencies.
Step 3: Configure AndroidManifest.xml (Android)
Add the necessary permissions and service declarations in your android/app/src/main/AndroidManifest.xml
file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.package.name">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="geofence_app">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2"/>
<receiver
android:name="com.transistorsoft.flutter.backgroundfetch.BackgroundFetch$BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<service
android:name="com.transistorsoft.flutter.backgroundfetch.BackgroundFetch$HeadlessTaskService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">
<intent-filter>
<action android:name="android.intent.action.HEADLESS_JS_TASK"/>
</intent-filter>
</service>
</application>
</manifest>
Ensure to request location permissions at runtime. Add the following lines inside the <application> tag:
<service
android:name="com.pravera.geofence.GeofenceService"
android:enabled="true"
android:exported="false"/>
<receiver
android:name="com.pravera.geofence.GeofenceBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
</receiver>
Step 4: Configure Info.plist (iOS)
Add the necessary permissions in your ios/Runner/Info.plist
file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to work.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs location access to work.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access to work.</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>geofence_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
Add the following keys with descriptions:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
Step 5: Request Permissions
Before using geofencing, request location permissions from the user. Use the permission_handler
plugin for this:
dependencies:
permission_handler: ^11.3.0
Request the necessary permissions in your Dart code:
import 'package:permission_handler/permission_handler.dart';
Future<void> requestPermissions() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.locationAlways,
Permission.locationWhenInUse,
].request();
print(statuses[Permission.locationAlways]);
print(statuses[Permission.locationWhenInUse]);
}
Call this function in your app’s initState
or before using geofencing functionalities.
Step 6: Implement Geofencing Logic
Create and manage geofences in your Flutter application.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geofence/geofence.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _activity = 'Unknown';
String _latitude = '';
String _longitude = '';
String _address = '';
@override
void initState() {
super.initState();
requestPermissions();
initGeofence();
}
Future<void> requestPermissions() async {
// Request location permissions
Map<Permission, PermissionStatus> statuses = await [
Permission.locationAlways,
Permission.locationWhenInUse,
].request();
print(statuses[Permission.locationAlways]);
print(statuses[Permission.locationWhenInUse]);
}
Future<void> initGeofence() async {
Geofence.initialize();
Geofence.getActivityStream()?.listen((Activity result) {
setState(() {
_activity = result.toString();
});
});
Geofence.getGeolocationStream()?.listen((Geolocation result) {
setState(() {
_latitude = result.latitude.toString();
_longitude = result.longitude.toString();
});
});
Geofence.getAddressStream()?.listen((Address result) {
setState(() {
_address = '${result.countryName}, ${result.adminArea}, ${result.locality}';
});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Geofencing in Flutter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Activity: $_activityn'),
Text('Latitude: $_latituden'),
Text('Longitude: $_longituden'),
Text('Address: $_addressn'),
ElevatedButton(
onPressed: () {
createGeofence();
},
child: Text('Create Geofence'),
),
ElevatedButton(
onPressed: () {
removeGeofence();
},
child: Text('Remove Geofence'),
),
],
),
),
),
);
}
Future<void> createGeofence() async {
try {
await Geofence.addGeolocation(
GeofenceRegion(
id: 'myGeofence',
latitude: 37.7749, // Example Latitude
longitude: -122.4194, // Example Longitude
radius: [
RadiusDescriptor(id: '100m', length: 100),
],
// Allows transitionType to be a String or a TransitionType Enum.
transitionType: TransitionType.EXIT,
),
);
print('Geofence created successfully');
} catch (e) {
print('Error creating geofence: $e');
}
}
Future<void> removeGeofence() async {
try {
await Geofence.removeGeolocation('myGeofence');
print('Geofence removed successfully');
} catch (e) {
print('Error removing geofence: $e');
}
}
}
In this example:
- The
initState
method initializes geofencing and requests location permissions. - The
createGeofence
method adds a geofence with specific coordinates and a radius. - The
removeGeofence
method removes the created geofence. - The UI displays the current activity, location, and address, along with buttons to create and remove geofences.
Testing Your Geofencing Implementation
Testing geofencing requires simulating location changes. You can use tools such as the Android emulator or Xcode’s simulator to mock locations and verify that your app responds correctly to geofence transitions.
Best Practices for Geofencing
- Handle Permissions Gracefully: Always check and request necessary permissions.
- Optimize Battery Usage: Use appropriate geofence radii and transition types to minimize battery drain.
- Handle Errors: Implement proper error handling to manage scenarios where location services are unavailable.
- Inform Users: Clearly explain why your app needs location permissions.
Conclusion
Implementing geofencing in Flutter can significantly enhance your app’s functionality by making it location-aware. By following this comprehensive guide, you can create applications that respond intelligently to user movements within specified geographic boundaries. Be sure to handle permissions, optimize battery usage, and thoroughly test your implementation to provide the best possible user experience.