Skip to content

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:

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


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.