Convenience library to receive user location updates and geofence events with minimal effort. Survives app kills and device reboots.
- Features
- Location Filtering
- Installation
- Initialization
- Quick Start
- API Reference
- Permissions
- Maestro UI Tests
- Contributing
- License
- Kotlin-first API with DSL builders and coroutines
- Geofence transitions: enter, exit, dwell
- Continuous location updates via
SharedFlow - Survives app kill and device reboot (via WorkManager)
- Auto-initializes via built-in ContentProvider (no external dependencies)
- Configurable update intervals, displacement, and priority
- Custom actions for geofence and location events
- minSdk 23, targets Android 15+
Raw GPS is noisy — especially indoors, where fixes can drift hundreds of meters. The library runs every location update through a filtering pipeline before it reaches your code or triggers a geofence event.
FusedLocationProvider
→ Accuracy gate — reject fixes worse than a threshold
→ Speed check — reject teleportation (configurable m/s cap)
→ Accuracy-weighted — ignore movement within the error radius
→ Provider quality — reject garbage network-provider fixes
→ Consistency buffer — require N consecutive samples before flipping state
→ Dwell confirmation — require the device to *stay* before emitting
→ Event deduplication — prevent double-fires from Play Services + client-side
All filters have sensible defaults and are configurable via LocationConfig and GeofenceBuilder. Unfiltered data is still available via LocationTracker.rawLocations.
implementation("net.kibotu:geofencer:3.0.0")Add the JitPack repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}Then add the dependency:
implementation("com.github.kibotu:geofencer:latest")The library auto-initializes using a ContentProvider — no manual setup needed.
The ContentProvider uses android:initOrder to control initialization priority relative to other providers. The default value is 100. Higher values mean the provider is initialized earlier.
Override the priority in your app's res/values/integers.xml (or any values resource file):
<resources>
<integer name="geofencer_init_priority">200</integer>
</resources>Add a boolean resource override in your app's res/values/ to disable the ContentProvider from initializing the library:
<resources>
<bool name="geofencer_auto_init_enabled">false</bool>
</resources>Then initialize manually in your Application.onCreate():
Geofencer.init(applicationContext)Alternatively, you can remove the provider entirely via manifest merging:
<provider
android:name="net.kibotu.geofencer.internal.GeofencerInitializer"
android:authorities="${applicationId}.geofencer-init"
tools:node="remove" />Geofencer.add {
latitude = 52.520008
longitude = 13.404954
radius = 200.0
label = "Berlin"
message = "Welcome to Berlin!"
transitions = setOf(Geofence.Transition.Enter, Geofence.Transition.Exit)
action<NotificationAction>()
}lifecycleScope.launch {
Geofencer.events.collect { event ->
Log.d("Geofence", "${event.transition.name} ${event.geofence.label}")
}
}Actions run even when the app is killed:
class NotificationAction : GeofenceAction() {
override fun onTriggered(context: Context, event: GeofenceEvent) {
// send notification, log analytics, etc.
}
}// list all active geofences
val all: List<Geofence> = Geofencer.geofences.value
// observe changes
Geofencer.geofences.collect { list -> /* update UI */ }
// look up by id
val fence: Geofence? = Geofencer["some-id"]
// remove
Geofencer.remove("some-id")
Geofencer.removeAll()LocationTracker.start(context) {
interval = 10.seconds
fastest = 5.seconds
displacement = 50f
action<LocationLogAction>()
}lifecycleScope.launch {
LocationTracker.locations.collect { location ->
Log.d("Location", "${location.latitude}, ${location.longitude}")
}
}class LocationLogAction : LocationAction() {
override fun onUpdate(context: Context, result: LocationResult) {
// persist, upload, etc.
}
}LocationTracker.stop(context)| Class | Description |
|---|---|
Geofencer |
Singleton to add/remove geofences and observe events via SharedFlow |
Geofence |
Data class holding coordinates, radius, transitions, and metadata |
GeofenceBuilder |
DSL builder for Geofence instances |
GeofenceEvent |
Emitted event containing the geofence, transition type, and triggering location |
GeofenceAction |
Abstract class for custom geofence event handlers (survives app kill) |
LocationTracker |
Singleton to start/stop location updates and observe via SharedFlow |
LocationConfig |
DSL builder for location request parameters |
LocationAction |
Abstract class for custom location update handlers (survives app kill) |
The library declares these permissions in its manifest (merged automatically):
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />Your app must request ACCESS_FINE_LOCATION (and ACCESS_BACKGROUND_LOCATION on Android 10+) at runtime before adding geofences or starting location tracking.
The demo app includes Maestro flows in the .maestro/ directory.
- Install Maestro:
curl -Ls "https://get.maestro.mobile.dev" | bash-
Connect an Android device or start an emulator.
-
Install the debug build:
./gradlew :app:installDebugRun all flows in the .maestro/ directory:
maestro test .maestro/Run a specific flow:
maestro test .maestro/geofence_full_test.yaml| Area | Details |
|---|---|
| Recording toggle | Toggles location tracking off and on |
| Battery / Performance | Switches between high-frequency and battery-saving mode |
| My location | Centers the map on the current position |
| Map styles | Cycles through all 5 styles (Pokemon GO, Steampunk, Light, Dark 3D, Satellite) |
| Event log | Opens the bottom sheet, verifies it shows, clears entries, closes |
| Geofence at current location | Runs the full wizard (location → radius → message) |
| Search + geofence | Searches for "Alexanderplatz Berlin", selects the result, completes the wizard |
Contributions are welcome! Please open an issue or submit a pull request.
MIT License
Copyright (c) 2026 Geofencer Developers
See LICENSE for the full text.