Filter Markers in Flutter
This tutorial shows how to filter which markers are displayed on the map based on categories, search text, or toggle buttons.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Filter by Category
Toggle different categories of markers on and off:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class FilterMarkersScreen extends StatefulWidget {
@override
_FilterMarkersScreenState createState() => _FilterMarkersScreenState();
}
class _FilterMarkersScreenState extends State<FilterMarkersScreen> {
MapMetricsController? mapController;
// Active category filters
Set<String> activeFilters = {'restaurant', 'hotel', 'museum', 'park'};
// All places with categories
final List<Map<String, dynamic>> places = [
{'id': '1', 'name': 'Le Bistrot', 'category': 'restaurant', 'position': LatLng(48.858, 2.340)},
{'id': '2', 'name': 'Café de Flore', 'category': 'restaurant', 'position': LatLng(48.854, 2.332)},
{'id': '3', 'name': 'Grand Hotel', 'category': 'hotel', 'position': LatLng(48.870, 2.330)},
{'id': '4', 'name': 'Hotel Paris', 'category': 'hotel', 'position': LatLng(48.862, 2.350)},
{'id': '5', 'name': 'Louvre', 'category': 'museum', 'position': LatLng(48.861, 2.338)},
{'id': '6', 'name': 'Musée d\'Orsay', 'category': 'museum', 'position': LatLng(48.860, 2.326)},
{'id': '7', 'name': 'Luxembourg Gardens', 'category': 'park', 'position': LatLng(48.846, 2.337)},
{'id': '8', 'name': 'Tuileries Garden', 'category': 'park', 'position': LatLng(48.863, 2.327)},
];
final Map<String, double> categoryHues = {
'restaurant': BitmapDescriptor.hueOrange,
'hotel': BitmapDescriptor.hueBlue,
'museum': BitmapDescriptor.hueViolet,
'park': BitmapDescriptor.hueGreen,
};
final Map<String, IconData> categoryIcons = {
'restaurant': Icons.restaurant,
'hotel': Icons.hotel,
'museum': Icons.museum,
'park': Icons.park,
};
Set<Marker> get filteredMarkers => places
.where((p) => activeFilters.contains(p['category']))
.map((place) => Marker(
markerId: MarkerId(place['id']),
position: place['position'],
icon: BitmapDescriptor.defaultMarkerWithHue(
categoryHues[place['category']] ?? BitmapDescriptor.hueRed,
),
infoWindow: InfoWindow(
title: place['name'],
snippet: place['category'],
),
))
.toSet();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Filter Markers')),
body: Column(
children: [
// Filter chips
Container(
padding: EdgeInsets.all(8),
color: Colors.grey[100],
child: Wrap(
spacing: 8,
children: categoryHues.keys.map((category) {
final isActive = activeFilters.contains(category);
return FilterChip(
avatar: Icon(
categoryIcons[category],
size: 18,
color: isActive ? Colors.white : Colors.grey,
),
label: Text(
'${category[0].toUpperCase()}${category.substring(1)}',
),
selected: isActive,
selectedColor: Colors.blue,
labelStyle: TextStyle(
color: isActive ? Colors.white : Colors.black87,
),
onSelected: (selected) {
setState(() {
if (selected) {
activeFilters.add(category);
} else {
activeFilters.remove(category);
}
});
},
);
}).toList(),
),
),
// Count
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
color: Colors.grey[50],
child: Row(
children: [
Text(
'Showing ${filteredMarkers.length} of ${places.length} places',
style: TextStyle(fontSize: 13, color: Colors.grey[600]),
),
Spacer(),
TextButton(
onPressed: () => setState(() =>
activeFilters = {'restaurant', 'hotel', 'museum', 'park'}),
child: Text('Show All'),
),
],
),
),
// Map
Expanded(
child: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(48.858, 2.340),
zoom: 14.0,
),
markers: filteredMarkers,
),
),
],
),
);
}
}Filter by Search Text
Search markers by name in real time:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class SearchFilterScreen extends StatefulWidget {
@override
_SearchFilterScreenState createState() => _SearchFilterScreenState();
}
class _SearchFilterScreenState extends State<SearchFilterScreen> {
MapMetricsController? mapController;
String searchQuery = '';
final List<Map<String, dynamic>> places = [
{'id': '1', 'name': 'Eiffel Tower', 'position': LatLng(48.8584, 2.2945)},
{'id': '2', 'name': 'Louvre Museum', 'position': LatLng(48.8606, 2.3376)},
{'id': '3', 'name': 'Notre-Dame', 'position': LatLng(48.8530, 2.3499)},
{'id': '4', 'name': 'Sacré-Cœur', 'position': LatLng(48.8867, 2.3431)},
{'id': '5', 'name': 'Arc de Triomphe', 'position': LatLng(48.8738, 2.2950)},
{'id': '6', 'name': 'Luxembourg Gardens', 'position': LatLng(48.8462, 2.3372)},
{'id': '7', 'name': 'Moulin Rouge', 'position': LatLng(48.8841, 2.3322)},
{'id': '8', 'name': 'Musée d\'Orsay', 'position': LatLng(48.8600, 2.3266)},
];
List<Map<String, dynamic>> get filteredPlaces => searchQuery.isEmpty
? places
: places
.where((p) =>
p['name'].toLowerCase().contains(searchQuery.toLowerCase()))
.toList();
Set<Marker> get markers => filteredPlaces
.map((place) => Marker(
markerId: MarkerId(place['id']),
position: place['position'],
infoWindow: InfoWindow(title: place['name']),
))
.toSet();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Search Places')),
body: Column(
children: [
// Search bar
Padding(
padding: EdgeInsets.all(8),
child: TextField(
decoration: InputDecoration(
hintText: 'Search places...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding: EdgeInsets.symmetric(horizontal: 12),
suffixIcon: searchQuery.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () => setState(() => searchQuery = ''),
)
: null,
),
onChanged: (value) => setState(() => searchQuery = value),
),
),
// Results count
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Text(
'${filteredPlaces.length} results',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
),
// Map
Expanded(
child: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(48.860, 2.330),
zoom: 13.0,
),
markers: markers,
),
),
],
),
);
}
}Next Steps
- Add Clusters — Group filtered markers into clusters
- Popup on Click — Show details when tapping filtered markers
- Markers and Annotations — Basic marker features
Tip: For large datasets, use Set instead of List for markers and filter with .where() — Flutter's MapMetrics widget efficiently diffs the marker set on each rebuild.