place_pickarte
A Flutter plugin for making pixel-by-pixel customizable map place pickers.
π Features
π¨ Fully Customizable: Adapt to any design system
πΊοΈ Google Maps: Built-in support with more providers coming
π Places Search: Autocomplete and location search
β¨ Smooth Animations: Responsive pin interactions
π Multiple Styles: Six pre-built map themes
π Production Ready: Complete example included
π± Screenshots
| Picker | Style | Search |
|---|---|---|
![]() |
![]() |
![]() |
π©΅ Want to say "thanks"?
Check UserOrient, my side project for Flutter apps to collect feedback from users.
πΉοΈ Usage
Setup first: We use Google Maps under the hood. Follow google_maps_flutter setup for API keys and native config.
Complete Place Picker
Copy example/lib/place_picker_page.dart which is a complete map place picker implemented using place_pickarte's plugin APIs. You get everything:
- π Search bar with autocomplete overlay
- π Animated pin that responds to map movement
- π± My location button with permission handling
- π Bottom sheet showing selected address
- β Continue button to confirm selection
- π¨ Styled components ready for your colors
That's it!, that's the main point: we give you a complete, production-ready place picker that you can customize to your brand. It already has a beautiful and minimal design that can go with any design system, and you can also easily customize it for your own app.
API Reference
Here's how to build your own place picker step by step:
1. Create the Configuration
PlacePickarteConfig(
// Required - Your Google Maps API keys
googleMapConfig: GoogleMapConfig(
iosApiKey: 'YOUR_IOS_KEY',
androidApiKey: 'YOUR_ANDROID_KEY',
),
// Optional - Where to start the map (if not provided, defaults to Baku, Azerbaijan)
initialLocation: Location(lat: 40.4093, lng: 49.8671),
initialZoom: 16.5,
// Optional - Should we try to get user's location first? (default: true)
myLocationAsInitial: true,
// Optional but recommended - Needed to show addresses in your UI
// Without this, you won't get readable addresses, just coordinates
googleMapsGeocoding: GoogleMapsGeocoding(apiKey: 'YOUR_KEY'),
// Optional - Customize search behavior
placesAutocompleteConfig: PlacesAutocompleteConfig(
region: 'az', // Bias results to this country
components: [Component(Component.country, 'az')], // Only show results from this country
language: 'en', // Language for results
types: ['establishment'], // What types of places to show
),
// Optional - Replace the default pin with your own
pinBuilder: (context, state) => YourCustomPin(state),
)
2. Create the Controller
final controller = PlacePickarteController(config: yourConfig);
// Don't forget to dispose it
@override
void dispose() {
controller.close();
super.dispose();
}
3. Add the Map
PlacePickarteMap(controller) // That's it, you have a working map with pin
4. Listen to Location Changes
// This gives you the selected location with full address
StreamBuilder<GeocodingResult?>(
stream: controller.currentLocationStream,
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading...');
final location = snapshot.data!;
return Text(location.formattedAddress ?? 'Unknown location');
},
)
5. Add Search (Optional)
// Listen to search results
StreamBuilder<List<Prediction>?>(
stream: controller.autocompleteResultsStream,
builder: (context, snapshot) {
final predictions = snapshot.data ?? [];
return ListView.builder(
itemCount: predictions.length,
itemBuilder: (context, index) {
final prediction = predictions[index];
return ListTile(
title: Text(prediction.description ?? ''),
onTap: () {
// Jump to this location
controller.selectAutocompleteItem(prediction);
},
);
},
);
},
)
// Trigger search
controller.searchAutocomplete('pizza'); // Search for pizza places
6. Add My Location Button (Optional)
ElevatedButton(
onPressed: () async {
final result = await controller.goToMyLocation();
// Handle different results
switch (result) {
case MyLocationResult.success:
// All good, map moved to user location
break;
case MyLocationResult.permissionDenied:
// Show dialog asking for permission
break;
case MyLocationResult.serviceNotEnabled:
// Ask user to enable GPS
break;
}
},
child: Text('My Location'),
)
7. Customize Pin Animation (Optional)
// The pin has two states: idle and dragging
pinBuilder: (context, state) {
return AnimatedContainer(
duration: Duration(milliseconds: 200),
// Move pin up when dragging
transform: Matrix4.translationValues(0, state == PinState.dragging ? -8 : 0, 0),
child: Icon(
state == PinState.dragging ? Icons.location_searching : Icons.location_on,
size: 72,
color: state == PinState.dragging ? Colors.grey : Colors.red,
),
);
}
8. Style the Map (Optional)
GoogleMapConfig(
googleMapStyle: GoogleMapStyles.dark, // Dark theme
googleMapStyle: GoogleMapStyles.night, // Night mode
googleMapStyle: GoogleMapStyles.retro, // Vintage look
googleMapStyle: GoogleMapStyles.silver, // Minimal gray
googleMapStyle: GoogleMapStyles.aubergine, // Purple theme
// Leave null for standard Google Maps
)
That's everything you need to know. The example file shows all of this working together in a real app.
π‘ Inspired from/by
- Forked and modified google_maps_webservice according to this package's needs, specifically for not supporting null-safety.


