geocode_cache 1.0.0
geocode_cache: ^1.0.0 copied to clipboard
A Flutter package for caching geocoding and place search results locally using Hive CE storage with Haversine distance matching. Minimizes API calls by serving nearby coordinates from cache.
geocode_cache #
A Flutter package that caches reverse geocoding and place search results locally using Hive CE. Reduces API calls by serving nearby coordinates from cache using Haversine distance matching.
Features #
- Haversine proximity matching — coordinates within a configurable radius (default 10m) return cached results
- Place search caching — Nominatim search results are cached per query
- TTL support — optional time-to-live expiration for cache entries
- Custom address formatting — override the default address format
- FIFO eviction — automatic eviction when cache reaches max size
- Manual cache management — clear all, clear by type, or evict stale entries
- Zero configuration — works out of the box with sensible defaults
Installation #
Add to your pubspec.yaml:
dependencies:
geocode_cache: ^1.0.0
Then run:
flutter pub get
The Hive type adapters are pre-generated and bundled — no
build_runnerstep needed.
Quick Start #
import 'package:geocode_cache/geocode_cache.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialise the service (uses default options)
await GeocodingService.instance.init();
// Get address — automatically cached
final address = await GeocodingService.instance
.getAddressFromCoordinates(-8.6705, 115.2126);
print(address); // "Jl. Raya Ubud, Ubud, Bali, Indonesia"
// Second call with nearby coordinates → served from cache (no API call)
final nearby = await GeocodingService.instance
.getAddressFromCoordinates(-8.6706, 115.2127);
print(nearby); // Same result, from cache
}
Configuration #
Call configure() before init() to customise behaviour:
GeocodingService.instance.configure(
options: const GeocodeCacheOptions(
cacheRadiusMeters: 20, // match within 20m (default: 10m)
maxAge: Duration(days: 7), // expire after 7 days (default: never)
maxCacheSize: 500, // keep up to 500 entries (default: 250)
userAgent: 'MyApp/1.0', // for Nominatim requests
),
);
await GeocodingService.instance.init();
Custom Address Format #
GeocodingService.instance.configure(
options: GeocodeCacheOptions(
addressFormatter: (street, locality, state, country) {
return '$locality, $country';
},
),
);
Options Reference #
| Option | Type | Default | Description |
|---|---|---|---|
cacheRadiusMeters |
double |
10.0 |
Distance threshold for cache hits (meters) |
boxName |
String |
'geocode_cache' |
Hive box name for geocode entries |
maxAge |
Duration? |
null |
Max age before entry expires. null = never |
maxCacheSize |
int |
250 |
Max entries before FIFO eviction kicks in |
userAgent |
String |
'geocode_cache/1.0' |
User-Agent for Nominatim HTTP requests |
addressFormatter |
Function? |
null |
Custom address string builder |
Usage #
Reverse Geocoding #
final address = await GeocodingService.instance
.getAddressFromCoordinates(latitude, longitude);
if (address != null) {
print('Resolved: $address');
} else {
print('Could not resolve address');
}
Place Search #
Search for places using Nominatim with automatic caching:
final results = await GeocodingService.instance.searchPlaces('Ubud, Bali');
for (final place in results) {
print('${place['display_name']} (${place['lat']}, ${place['lon']})');
}
Cache Management #
// Check cache sizes
print(GeocodingService.instance.cacheSize); // geocode entries
print(GeocodingService.instance.placeSearchCacheSize); // place search entries
// Clear everything
await GeocodingService.instance.clearCache();
// Clear selectively
await GeocodingService.instance.clearGeocodeCache();
await GeocodingService.instance.clearPlaceSearchCache();
// Evict entries older than 30 days
final evicted = await GeocodingService.instance
.evictEntriesOlderThan(const Duration(days: 30));
print('Evicted $evicted stale entries');
// Inspect cached entries
final entries = GeocodingService.instance.allCachedEntries;
for (final entry in entries) {
print('${entry.latitude}, ${entry.longitude} → ${entry.address}');
}
Cleanup #
// Close Hive boxes and reset the singleton
await GeocodingService.instance.dispose();
After dispose(), calling GeocodingService.instance returns a fresh instance that can be configured and initialised again.
How It Works #
┌─────────────────────────────────────────────────────┐
│ getAddressFromCoordinates() │
└─────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Search cache for entry within cacheRadiusMeters │
│ (Haversine distance, skip expired entries) │
└──────────┬──────────────────────────┬───────────────┘
│ │
Cache HIT Cache MISS
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────────────────┐
│ Return cached │ │ Call platform geocoding API │
│ address │ │ Save result to cache │
└──────────────────┘ │ Return address │
└─────────────────────────────┘
Platform Setup #
This package uses the geocoding package for reverse geocoding. Follow its platform setup:
Android #
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
iOS #
Add to ios/Runner/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to resolve addresses.</string>
Web #
No additional setup required. The geocoding package uses the browser's built-in geocoding capabilities.
Example #
A complete example integrating with a Flutter app:
import 'package:flutter/material.dart';
import 'package:geocode_cache/geocode_cache.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
GeocodingService.instance.configure(
options: const GeocodeCacheOptions(
cacheRadiusMeters: 15,
maxAge: Duration(days: 7),
),
);
await GeocodingService.instance.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Geocode Cache Demo')),
body: Center(
child: ElevatedButton(
onPressed: () async {
final address = await GeocodingService.instance
.getAddressFromCoordinates(-8.6705, 115.2126);
debugPrint('Address: $address');
},
child: const Text('Get Address'),
),
),
),
);
}
}
Contributing #
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -am 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
Development #
# Get dependencies
flutter pub get
# Run tests
flutter test
# Regenerate Hive adapters (only if models change)
dart run build_runner build --delete-conflicting-outputs
# Analyze code
flutter analyze
License #
MIT — see LICENSE for details.