flexible_location_picker 0.1.0+7
flexible_location_picker: ^0.1.0+7 copied to clipboard
A Flutter package to pick locations flexibly, supporting both web and mobile platforms.
Flexible Location Picker #
A powerful, highly customizable, and performant Flutter package for location and address selection on a map. It offers a seamless user experience with search autocomplete, real-time geolocation, and flexible integration options.
β¨ Features #
- Flexible Integration: Embed the picker as a full page, a modal bottom sheet, or an integrated widget within any part of your UI.
- Intuitive Address Search: Fast and accurate address autocomplete powered by Google Places API.
- Precise Geolocation: Detects current device location with caching for optimal performance and robust error handling.
- Animated Map Marker: Smooth animations for the central map marker provide clear visual feedback during map interaction.
- Customizable Map Controls: Includes configurable zoom controls and a "My Location" button.
- Performance Optimized:
- Caching: Caches location and address data to minimize redundant API calls.
- Debouncing: Implements debouncing for search queries to prevent excessive API requests.
- Efficient Filtering: Limits search results for responsiveness.
- Granular Configuration: Extensive options via
LocationPickerConfig
to customize colors, texts, map types, and behavior. - Clean Architecture: Built with a modular structure, separating concerns with dedicated services (
LocationService
,AddressService
) and a centralChangeNotifier
-based controller for state management. - User-Friendly Error Handling: Clear messages for location service issues, permission denials, and search errors.
πΈ Demo : See full demo #
π Installation #
Add this to your package's pubspec.yaml
file:
dependencies:
flexible_location_picker: ^latest_version # Replace with the actual latest version
Then, run flutter pub get
.
Important: API Key Setup
To use Google Maps and Places API functionalities, you must obtain an API key from the Google Cloud Platform and enable the following APIs:
- Maps SDK for Android
- Maps SDK for iOS
- Places API
- Geocoding API (used for reverse geocoding LatLng to address)
Configure your API Key:
-
Android (
android/app/src/main/AndroidManifest.xml
): Place your API key inside the<application>
tag:<meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_Maps_API_KEY" />
Crucial: Apply Android app restrictions to your API key in the Google Cloud Console, including your app's package name and SHA-1 certificate fingerprint.
-
iOS (
ios/Runner/AppDelegate.swift
orAppDelegate.m
): Add the API key beforeGeneratedPluginRegistrant.register
:// AppDelegate.swift import GoogleMaps // Don't forget this import func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { GMSServices.provideAPIKey("YOUR_Maps_API_KEY") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
Crucial: Apply iOS app restrictions to your API key in the Google Cloud Console, using your app's Bundle ID.
-
Pass API Key to
LocationPickerConfig
: You must pass your Google Places API key directly to theLocationPickerConfig
for the search functionality:LocationPickerWidget( config: LocationPickerConfig( googlePlacesApiKey: 'YOUR_GOOGLE_PLACES_API_KEY', // ... other configurations ), // ... )
π» Usage #
The flexible_location_picker
offers three primary integration modes:
1. As a Full Page #
import 'package:flexible_location_picker/flexible_location_picker.dart';
import 'package:flutter/material.dart';
class MyLocationSelectionPage extends StatelessWidget {
const MyLocationSelectionPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: LocationPickerWidget(
config: LocationPickerConfig(
googlePlacesApiKey: 'YOUR_GOOGLE_PLACES_API_KEY',
showSearchField: true,
showCurrentLocationButton: true,
initialZoom: 15.0,
confirmButtonText: 'Select This Place', // Customize button text
// ... more configurations
),
onLocationSelected: (locationData) {
// Handle the selected location data
Navigator.pop(context, locationData); // Pop the page with the result
print('Selected Location: ${locationData.address}');
},
),
);
}
}
// To open this page:
// final selectedLocation = await Navigator.push<LocationData?>(
// context,
// MaterialPageRoute(builder: (context) => const MyLocationSelectionPage()),
// );
// if (selectedLocation != null) { /* Do something with selectedLocation */ }
2. As a Modal Bottom Sheet #
import 'package:flexible_location_picker/flexible_location_picker.dart';
import 'package:flutter/material.dart';
// To show as a modal bottom sheet:
Future<void> _showLocationPickerBottomSheet(BuildContext context) async {
final selectedLocation = await LocationPickerBottomSheet.show(
context: context,
height: MediaQuery.of(context).size.height * 0.8, // 80% of screen height
isDismissible: true,
enableDrag: true,
config: LocationPickerConfig(
googlePlacesApiKey: 'YOUR_GOOGLE_PLACES_API_KEY',
showSearchField: true,
showCurrentLocationButton: true,
initialZoom: 16.0,
confirmButtonText: 'Confirm Selection',
// ... more configurations
),
);
if (selectedLocation != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Location selected: ${selectedLocation.address}')),
);
}
}
// Call this from a button or wherever needed:
// ElevatedButton(
// onPressed: () => _showLocationPickerBottomSheet(context),
// child: const Text('Pick Location (Bottom Sheet)'),
// )
3. As an Integrated Widget #
import 'package:flexible_location_picker/flexible_location_picker.dart';
import 'package:flutter/material.dart';
class MyEmbeddedPicker extends StatefulWidget {
const MyEmbeddedPicker({super.key});
@override
State<MyEmbeddedPicker> createState() => _MyEmbeddedPickerState();
}
class _MyEmbeddedPickerState extends State<MyEmbeddedPicker> {
LocationData? _selectedLocation;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Optionally display current selection
if (_selectedLocation != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Current Pick: ${_selectedLocation!.address}'),
),
Expanded( // Or provide a fixed height like height: 400
child: LocationPickerWidget(
config: LocationPickerConfig(
googlePlacesApiKey: 'YOUR_GOOGLE_PLACES_API_KEY',
showSearchField: true,
showCurrentLocationButton: true,
showConfirmButton: false, // Often hidden when embedded
initialZoom: 14.0,
// ... more configurations
),
onLocationSelected: (locationData) {
setState(() {
_selectedLocation = locationData;
});
print('Embedded picker selected: ${locationData.address}');
},
),
),
],
);
}
}
βοΈ Configuration #
The LocationPickerConfig
class provides extensive customization options:
LocationPickerConfig(
// API Key
googlePlacesApiKey: 'YOUR_GOOGLE_PLACES_API_KEY', // Required for search
// Map Initial State
initialPosition: const LatLng(34.0522, -118.2437), // Los Angeles default
initialZoom: 15.0,
minZoom: 2.0,
maxZoom: 20.0,
mapType: 'normal', // 'normal', 'satellite', 'hybrid', 'terrain'
mapStyle: '[{"featureType":"poi", "stylers":[{"visibility":"off"}]}]', // JSON style string
// Feature Visibility
showSearchField: true,
showCurrentLocationButton: true,
showConfirmButton: true,
showZoomControls: true,
enableMyLocation: true, // Shows blue dot for current location
// Text Customization
confirmButtonText: 'Select Location',
searchHintText: 'Enter address or drag map...',
loadingText: 'Fetching location...',
errorText: 'Failed to load location.',
noLocationText: 'Location not found.',
currentLocationText: 'Use Current Location',
// Style Customization
markerColor: Colors.deepOrange,
primaryColor: Colors.blue, // Primary accent color
backgroundColor: Colors.white,
textColor: Colors.black87,
searchFieldDecoration: const InputDecoration( // Custom InputDecoration for search field
filled: true,
fillColor: Colors.white,
hintText: 'Search...',
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
),
confirmButtonStyle: ElevatedButton.styleFrom( // Custom ButtonStyle for confirm button
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
),
// Performance & Animation
debounceTime: const Duration(milliseconds: 800), // Delay for reverse geocoding after map move
animationDuration: const Duration(milliseconds: 300), // Marker animation duration
searchRadius: 50000, // Search bias radius in meters (50 km)
// autoCompleteSessionToken: 'custom_session_token', // Advanced: for billing optimization if managing manually
)
π οΈ Advanced Usage #
Custom Map Styling #
You can provide a JSON string to mapStyle
in LocationPickerConfig
for granular control over map appearance. Generate styles using Google Cloud's styling wizard.
Billing Optimization with Session Tokens #
The package internally generates session tokens for Google Places API calls (autocomplete
and getDetailsByPlaceId
) to ensure optimal billing (a session counts as one billable transaction). You generally do not need to manage autoCompleteSessionToken
manually unless you have a specific advanced use case.
Error Handling #
The LocationPickerController
provides detailed error messages accessible via controller.error
. Your UI (like _buildErrorState
in LocationPickerWidget
) can leverage these messages to inform users about permission issues, disabled location services, or API errors.
π Example #
Explore the /example
folder in the repository for a complete demonstration of all features and integration types.
To run the example:
cd example
flutter pub get
flutter run
Remember to configure your Google Maps API key in the example/constants.dart
file and the Android/iOS native project files as described in the Installation section.
π€ Contributing #
Contributions are warmly welcome! If you have ideas for improvements, new features, or bug fixes, please feel free to open an issue or submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/your-awesome-feature
) - Commit your changes (
git commit -m 'feat: Add some awesome feature'
) - Push to the branch (
git push origin feature/your-awesome-feature
) - Open a Pull Request
π License #
This project is licensed under the MIT License - see the LICENSE file for details.
π§βπ» About the Author #
Created by Omar Farouk