LocationIQ API Client for Dart
A comprehensive, production-ready Dart client for the LocationIQ API. This package provides easy access to LocationIQ's complete suite of location services with type-safe models, comprehensive error handling, and extensive testing.
π Features
Complete LocationIQ API Coverage
- π Forward Geocoding - Convert addresses to coordinates (Free-form, Structured, and Postal Code)
- π Reverse Geocoding - Convert coordinates to human-readable addresses
- β‘ Address Autocomplete - Real-time search suggestions with partial queries
- π’ Nearby Points of Interest - Find restaurants, schools, hospitals, gas stations, etc.
- πΊοΈ Directions API - Route planning with multiple transportation profiles
- π Timezone API - Get timezone information for any coordinate
- π° Balance API - Monitor account usage and remaining balance
Developer Experience
- π‘οΈ Type-safe API with null safety and comprehensive models
- π― Robust error handling with specific exception types
- β‘ Configurable timeout, HTTP client, and base URL
- π§ͺ 89 unit tests with 100% mock coverage
- π Extensive documentation with real-world examples
- ποΈ Clean architecture with service separation
π¦ Installation
Add this to your package's pubspec.yaml
file:
dependencies:
location_iq: ^1.1.0
Then run:
dart pub get
π Getting Your API Key
To use this package, you'll need a LocationIQ API key:
- Sign up for a free account at https://locationiq.com/
- Verify your email and complete the registration process
- Navigate to your dashboard and find the "Access Tokens" section
- Copy your API key - it will look something like
pk.12345abcdef...
Free Tier Limits
LocationIQ offers a generous free tier:
- 60,000 requests per month
- 2 requests per second
- Access to all API endpoints (geocoding, reverse geocoding, autocomplete, etc.)
API Key Security
β οΈ Important: Keep your API key secure and never commit it to version control.
// β Don't do this - hardcoded API key
final client = LocationIQClient(apiKey: 'pk.your_actual_key_here');
// β
Do this - use environment variables or secure storage
final apiKey = Platform.environment['LOCATIONIQ_API_KEY'] ??
await SecureStorage.read('locationiq_api_key');
final client = LocationIQClient(apiKey: apiKey);
π Quick Start
import 'package:location_iq/location_iq.dart';
void main() async {
final client = LocationIQClient(
apiKey: 'your_api_key_here',
);
try {
// Search for a location
final locations = await client.forwardFreeform.search(
query: 'Buckingham Palace, London',
limit: 5,
);
for (final location in locations) {
print('Location: ${location.displayName}');
print('Coordinates: ${location.lat}, ${location.lon}');
}
} on LocationIQException catch (e) {
print('Error: ${e.message}');
} finally {
client.dispose(); // Clean up resources
}
}
π Complete API Reference
π Forward Geocoding
Convert addresses and place names to geographic coordinates.
Free-form Search
Search using natural language queries:
final locations = await client.forwardFreeform.search(
query: 'Central Park, New York City',
limit: 10,
acceptLanguage: 'en',
addressdetails: 1,
dedupe: 1,
extratags: 1,
namedetails: 1,
bounded: 1,
viewbox: '-74.1,40.6,-73.8,40.9', // NYC bounding box
countrycodes: 'us',
);
// Access detailed information
for (final location in locations) {
print('Name: ${location.displayName}');
print('Lat/Lon: ${location.lat}, ${location.lon}');
print('Type: ${location.type}');
print('Importance: ${location.importance}');
if (location.address != null) {
print('City: ${location.address!.city}');
print('Country: ${location.address!.country}');
}
}
Structured Search
Search using structured address components:
final results = await client.forwardStructured.search(
street: '221B Baker Street',
city: 'London',
county: 'Greater London',
state: 'England',
country: 'United Kingdom',
postalcode: 'NW1 6XE',
addressdetails: 1,
limit: 5,
);
Postal Code Search
Search specifically by postal codes:
final locations = await client.forwardPostalcode.search(
postalcode: '10001',
countrycodes: 'us',
addressdetails: 1,
limit: 10,
);
π Reverse Geocoding
Convert coordinates to human-readable addresses:
final address = await client.reverse.reverseGeocode(
lat: '51.5074',
lon: '-0.1278',
language: 'en',
addressdetails: 1,
extratags: 1,
namedetails: 1,
zoom: 18,
);
print('Address: ${address.displayName}');
if (address.address != null) {
print('Street: ${address.address!.road}');
print('City: ${address.address!.city}');
print('Postcode: ${address.address!.postcode}');
}
β‘ Address Autocomplete
Get real-time search suggestions as users type:
final suggestions = await client.autocomplete.suggest(
query: 'Buckingham Pal',
countryCode: 'gb',
limit: 8,
addressDetails: 1,
tag: 'place:palace',
);
for (final suggestion in suggestions) {
print('Suggestion: ${suggestion.displayName}');
print('Place ID: ${suggestion.placeId}');
}
π’ Nearby Points of Interest
Find nearby businesses, amenities, and landmarks:
// Find restaurants near a location
final restaurants = await client.nearbyPoi.findNearby(
lat: 40.7589,
lon: -73.9851, // Times Square
tag: 'amenity:restaurant',
radius: 1000, // 1km radius
limit: 20,
);
// Find multiple types of amenities
final amenities = await client.nearbyPoi.findNearby(
lat: 51.5074,
lon: -0.1278, // London
tag: 'amenity:hospital,amenity:school,amenity:bank',
radius: 2000,
limit: 50,
);
for (final poi in amenities) {
print('Name: ${poi.displayName}');
print('Type: ${poi.type}');
print('Distance: ${poi.distance}m');
print('Tags: ${poi.extratags}');
}
Popular POI Categories:
amenity:restaurant
- Restaurantsamenity:hospital
- Hospitalsamenity:school
- Schoolsamenity:bank
- Banksamenity:gas_station
- Gas stationsshop:supermarket
- Supermarketstourism:hotel
- Hotelsamenity:pharmacy
- Pharmacies
πΊοΈ Directions & Routing
Get detailed routing information between locations:
// Get driving directions
final route = await client.directions.getDirections(
coordinates: [
[-0.1278, 51.5074], // London
[2.3522, 48.8566], // Paris
],
profile: 'driving',
steps: true,
geometries: 'geojson',
overview: 'full',
annotations: true,
);
print('Distance: ${route.distance}m');
print('Duration: ${route.duration}s');
print('Routes: ${route.routes.length}');
// Access detailed route steps
for (final routeInfo in route.routes) {
print('Route distance: ${routeInfo.distance}m');
print('Route duration: ${routeInfo.duration}s');
if (routeInfo.legs != null) {
for (final leg in routeInfo.legs!) {
print('Leg: ${leg.distance}m, ${leg.duration}s');
if (leg.steps != null) {
for (final step in leg.steps!) {
print('Step: ${step.instruction}');
print('Distance: ${step.distance}m');
}
}
}
}
}
Available Profiles:
driving
- Car routingwalking
- Pedestrian routingcycling
- Bicycle routing
π Timezone Information
Get timezone data for any coordinate:
final timezone = await client.timezone.getTimezone(
lat: 40.7589,
lon: -73.9851, // New York
);
print('Timezone: ${timezone.timezone}');
print('UTC Offset: ${timezone.utcOffset}');
print('Local Time: ${timezone.localtime}');
print('DST Active: ${timezone.dst}');
// Additional timezone info
print('Country Code: ${timezone.countryCode}');
print('Country Name: ${timezone.countryName}');
print('Region: ${timezone.region}');
π° Account Balance
Monitor your API usage and account balance:
final balance = await client.balance.getBalance();
print('Status: ${balance.status}');
print('Balance: ${balance.balance}');
if (balance.billing != null) {
print('Plan: ${balance.billing!.name}');
print('Requests Used: ${balance.billing!.requests}');
if (balance.billing!.requestsRemaining != null) {
print('Requests Remaining: ${balance.billing!.requestsRemaining}');
}
}
βοΈ Configuration & Advanced Usage
Custom HTTP Client
Use your own HTTP client for custom configurations:
import 'package:http/http.dart' as http;
final customClient = http.Client();
final locationIQ = LocationIQClient(
apiKey: 'your_api_key_here',
httpClient: customClient,
);
// Don't forget to dispose both clients
locationIQ.dispose();
customClient.close();
Custom Timeout
Configure request timeouts:
final locationIQ = LocationIQClient(
apiKey: 'your_api_key_here',
timeout: Duration(seconds: 30), // Default is 15 seconds
);
Custom Base URL
Use different LocationIQ endpoints or your own proxy:
final locationIQ = LocationIQClient(
apiKey: 'your_api_key_here',
baseUrl: 'https://eu1.locationiq.com/v1', // EU endpoint
// baseUrl: 'https://us1.locationiq.com/v1', // US endpoint
);
Service Access
Access individual services directly:
final client = LocationIQClient(apiKey: 'your_key');
// All services are lazy-loaded and cached
final geocoding = client.forwardFreeform;
final reverse = client.reverse;
final autocomplete = client.autocomplete;
final nearbyPoi = client.nearbyPoi;
final directions = client.directions;
final timezone = client.timezone;
final balance = client.balance;
π‘οΈ Error Handling
The library provides comprehensive error handling with specific exception types for different scenarios:
try {
final results = await client.forwardFreeform.search(query: 'London');
} on AuthenticationException catch (e) {
// Invalid API key
print('Authentication failed: ${e.message}');
print('Status code: ${e.statusCode}');
} on AuthorizationException catch (e) {
// Domain not authorized for API key
print('Authorization failed: ${e.message}');
} on RateLimitException catch (e) {
// Rate limit exceeded
print('Rate limit exceeded: ${e.message}');
print('Try again later');
} on BadRequestException catch (e) {
// Invalid request parameters
print('Bad request: ${e.message}');
print('Check your parameters');
} on NotFoundException catch (e) {
// No results found
print('No results found: ${e.message}');
} on ServerException catch (e) {
// Server-side error (5xx)
print('Server error: ${e.message}');
print('Status code: ${e.statusCode}');
} on NetworkException catch (e) {
// Network connectivity issues
print('Network error: ${e.message}');
print('Check your internet connection');
} on UnexpectedException catch (e) {
// Unexpected errors
print('Unexpected error: ${e.message}');
} on LocationIQException catch (e) {
// Catch-all for any LocationIQ-related errors
print('LocationIQ error: ${e.message}');
} catch (e) {
// Non-LocationIQ errors
print('General error: $e');
}
Exception Types
Exception | Description | Common Causes |
---|---|---|
AuthenticationException |
Invalid API key | Wrong API key, expired key |
AuthorizationException |
Unauthorized domain | Domain not registered for API key |
RateLimitException |
Rate limit exceeded | Too many requests per minute/hour |
BadRequestException |
Invalid request | Missing required parameters, invalid format |
NotFoundException |
No results found | Location doesn't exist, typo in query |
ServerException |
Server-side error | LocationIQ service issues |
NetworkException |
Network issues | No internet, DNS resolution failure |
UnexpectedException |
Unexpected error | Parsing issues, unknown response format |
ποΈ Architecture & Models
Client Architecture
The LocationIQClient
provides a clean, service-oriented architecture:
LocationIQClient
βββ forwardFreeform (FreeformForwardGeocodingService)
βββ forwardStructured (StructuredForwardGeocodingService)
βββ forwardPostalcode (PostalCodeForwardGeocodingService)
βββ reverse (ReverseGeocodingService)
βββ autocomplete (AutocompleteService)
βββ nearbyPoi (NearbyPoiService)
βββ directions (DirectionsService)
βββ timezone (TimezoneService)
βββ balance (BalanceService)
Data Models
All API responses are parsed into type-safe Dart models:
ForwardGeocodingResult
class ForwardGeocodingResult {
final String placeId;
final String licence;
final String osmType;
final String osmId;
final List<String> boundingbox;
final String lat;
final String lon;
final String displayName;
final String type;
final double importance;
final Address? address;
// ... additional fields
}
Address
class Address {
final String? houseNumber;
final String? road;
final String? neighbourhood;
final String? suburb;
final String? city;
final String? county;
final String? state;
final String? postcode;
final String? country;
final String? countryCode;
// ... additional fields
}
DirectionsResult
class DirectionsResult {
final String code;
final List<Route> routes;
final List<Waypoint> waypoints;
final double? distance;
final double? duration;
}
NearbyPoiResult
class NearbyPoiResult {
final String placeId;
final String displayName;
final String type;
final double lat;
final double lon;
final double? distance;
final Map<String, dynamic>? extratags;
// ... additional fields
}
π§ͺ Testing
The package includes comprehensive testing with 89 unit tests covering all services:
# Run all tests
dart test
# Run tests with coverage
dart test --coverage
# Run specific test file
dart test test/unit/directions_service_test.dart
# Run tests in verbose mode
dart test --reporter=expanded
Test Coverage
- β 89 unit tests covering all services
- β 100% mock coverage for HTTP requests
- β Error scenario testing for all exception types
- β Parameter validation testing
- β JSON parsing and model testing
Running Static Analysis
# Analyze code quality
dart analyze
# Check formatting
dart format --output=none --set-exit-if-changed .
# Generate code (if needed)
dart run build_runner build
π± Platform Support
This package supports all platforms where Dart runs:
- β Flutter mobile (iOS, Android)
- β Flutter web
- β Flutter desktop (Windows, macOS, Linux)
- β Dart CLI applications
- β Dart server applications
π Migration Guide
From v1.0.x to v1.1.0
Version 1.1.0 adds new services while maintaining backward compatibility:
// v1.0.x - Still works
final client = LocationIQClient(apiKey: 'key');
final results = await client.forwardFreeform.search(query: 'London');
// v1.1.0 - New features available
final pois = await client.nearbyPoi.findNearby(lat: 51.5, lon: -0.1, tag: 'amenity:restaurant');
final route = await client.directions.getDirections(coordinates: [[0,0], [1,1]]);
final timezone = await client.timezone.getTimezone(lat: 40.7, lon: -74.0);
final balance = await client.balance.getBalance();
New in v1.1.0:
- β Nearby POI service
- β Directions/routing service
- β Timezone service
- β Balance monitoring service
- π§ Enhanced error handling
- π Improved documentation
ποΈ Project Structure
lib/
βββ location_iq.dart # Main export file
βββ src/
βββ config/
β βββ api_config.dart # API configuration
βββ core/
β βββ base/
β β βββ base_service.dart # Base service class
β βββ error/
β β βββ error_handler.dart # Error handling logic
β β βββ exceptions.dart # Exception definitions
β βββ http/
β βββ http_client.dart # HTTP client wrapper
β βββ http_status.dart # HTTP status codes
β βββ request_builder.dart # Request building utilities
βββ models/
β βββ models.dart # Model exports
β βββ address/
β β βββ address.dart # Address model
β β βββ address.g.dart # Generated JSON serialization
β βββ balance/
β β βββ balance_result.dart # Balance API models
β βββ geocoding/
β β βββ autocomplete_result.dart # Autocomplete models
β β βββ forward_geocoding_result.dart # Forward geocoding models
β β βββ reverse_result.dart # Reverse geocoding models
β βββ nearby/
β β βββ nearby_poi_result.dart # Nearby POI models
β βββ routing/
β β βββ directions_result.dart # Directions API models
β βββ timezone/
β βββ timezone_result.dart # Timezone API models
βββ services/
βββ autocomplete/
β βββ autocomplete.dart # Autocomplete service
βββ balance/
β βββ balance_service.dart # Balance service
βββ forward_geocoding/
β βββ freeform_forward_geocoding.dart # Free-form geocoding
β βββ postal_code_forward_geocoding.dart # Postal code geocoding
β βββ structured_forward_geocoding.dart # Structured geocoding
βββ nearby/
β βββ nearby_poi_service.dart # Nearby POI service
βββ reverse_geocoding/
β βββ reverse_geocoding.dart # Reverse geocoding service
βββ routing/
β βββ directions_service.dart # Directions service
βββ timezone/
βββ timezone_service.dart # Timezone service
π€ Contributing
Contributions are welcome! We appreciate bug reports, feature requests, and code contributions.
Development Setup
- Fork the repository
git clone https://github.com/your-username/location_iq.git
cd location_iq
- Install dependencies
dart pub get
- Run tests
dart test
- Run static analysis
dart analyze
Contributing Guidelines
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Write tests for your changes
- Ensure all tests pass (
dart test
) - Run static analysis (
dart analyze
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Code Style
- Follow Dart style guide
- Use
dart format
to format code - Add documentation for public APIs
- Write tests for new functionality
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Changelog
See CHANGELOG.md for a detailed list of changes and migration guides.
π Support & Resources
- π Documentation: LocationIQ API Docs
- π Get API Key: LocationIQ Registration
- π° Pricing & Plans: LocationIQ Pricing
- π Bug Reports: GitHub Issues
- π‘ Feature Requests: GitHub Issues
- π¬ Discussions: GitHub Discussions
Helpful Links
- Get LocationIQ API Key - Sign up for free access
- LocationIQ Dashboard - Manage your API keys and usage
- LocationIQ Pricing - Compare plans and features
- LocationIQ API Documentation - Complete API reference
- Dart Package Guidelines
If you find this package helpful, please consider:
- β Starring the repository
- π Reporting issues you encounter
- π Contributing new features or improvements
- π’ Sharing with other developers
Made with β€οΈ for the Dart and Flutter community.