Skip to content

Filter Features by Toggle List in Flutter

This tutorial shows how to filter map markers using a toggle list of categories — like a checkbox or chip filter panel.

Prerequisites

Before you begin, ensure you have:

Chip-Based Category Filter

Toggle categories on/off with filter chips:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class ToggleFilterScreen extends StatefulWidget {
  @override
  _ToggleFilterScreenState createState() => _ToggleFilterScreenState();
}

class _ToggleFilterScreenState extends State<ToggleFilterScreen> {
  MapMetricsController? mapController;

  final Map<String, bool> activeFilters = {
    'Restaurant': true,
    'Hotel': true,
    'Museum': true,
    'Park': true,
  };

  final Map<String, Color> categoryColors = {
    'Restaurant': Colors.red,
    'Hotel': Colors.blue,
    'Museum': Colors.purple,
    'Park': Colors.green,
  };

  final Map<String, double> categoryHues = {
    'Restaurant': BitmapDescriptor.hueRed,
    'Hotel': BitmapDescriptor.hueBlue,
    'Museum': BitmapDescriptor.hueViolet,
    'Park': BitmapDescriptor.hueGreen,
  };

  final List<Map<String, dynamic>> places = [
    {'name': 'Le Jules Verne', 'category': 'Restaurant', 'lat': 48.8580, 'lng': 2.2945},
    {'name': 'Cafe de Flore', 'category': 'Restaurant', 'lat': 48.8540, 'lng': 2.3326},
    {'name': 'Chez Janou', 'category': 'Restaurant', 'lat': 48.8570, 'lng': 2.3650},
    {'name': 'Hotel Ritz', 'category': 'Hotel', 'lat': 48.8682, 'lng': 2.3285},
    {'name': 'Hotel Lutetia', 'category': 'Hotel', 'lat': 48.8510, 'lng': 2.3268},
    {'name': 'Hotel Plaza', 'category': 'Hotel', 'lat': 48.8716, 'lng': 2.3044},
    {'name': 'Louvre', 'category': 'Museum', 'lat': 48.8606, 'lng': 2.3376},
    {'name': 'Musee d\'Orsay', 'category': 'Museum', 'lat': 48.8600, 'lng': 2.3266},
    {'name': 'Rodin Museum', 'category': 'Museum', 'lat': 48.8554, 'lng': 2.3158},
    {'name': 'Luxembourg Gardens', 'category': 'Park', 'lat': 48.8462, 'lng': 2.3372},
    {'name': 'Tuileries Garden', 'category': 'Park', 'lat': 48.8634, 'lng': 2.3275},
    {'name': 'Champ de Mars', 'category': 'Park', 'lat': 48.8557, 'lng': 2.2986},
  ];

  List<Map<String, dynamic>> get visiblePlaces {
    return places.where((p) => activeFilters[p['category']] == true).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Filter by Category')),
      body: Column(
        children: [
          // Filter chips
          Container(
            padding: EdgeInsets.all(12),
            color: Colors.white,
            child: Wrap(
              spacing: 8,
              children: activeFilters.keys.map((category) {
                final isActive = activeFilters[category]!;
                final color = categoryColors[category]!;
                return FilterChip(
                  label: Text(category),
                  selected: isActive,
                  selectedColor: color.withOpacity(0.2),
                  checkmarkColor: color,
                  onSelected: (selected) {
                    setState(() {
                      activeFilters[category] = selected;
                    });
                  },
                );
              }).toList(),
            ),
          ),
          // Count bar
          Container(
            padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
            color: Colors.grey[100],
            width: double.infinity,
            child: Text(
              '${visiblePlaces.length} of ${places.length} places shown',
              style: TextStyle(color: Colors.grey[600], fontSize: 12),
            ),
          ),
          // Map
          Expanded(
            child: MapMetrics(
              styleUrl:
                  'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (MapMetricsController controller) {
                mapController = controller;
              },
              initialCameraPosition: CameraPosition(
                target: LatLng(48.8566, 2.3300),
                zoom: 13.0,
              ),
              markers: visiblePlaces.map((place) {
                return Marker(
                  markerId: MarkerId(place['name']),
                  position: LatLng(place['lat'], place['lng']),
                  icon: BitmapDescriptor.defaultMarkerWithHue(
                    categoryHues[place['category']]!,
                  ),
                  infoWindow: InfoWindow(
                    title: place['name'],
                    snippet: place['category'],
                  ),
                );
              }).toSet(),
            ),
          ),
        ],
      ),
    );
  }
}

Checkbox Toggle List with Counts

Show categories in a collapsible drawer with checkbox toggles and counts:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class CheckboxFilterScreen extends StatefulWidget {
  @override
  _CheckboxFilterScreenState createState() => _CheckboxFilterScreenState();
}

class _CheckboxFilterScreenState extends State<CheckboxFilterScreen> {
  MapMetricsController? mapController;
  bool showFilters = true;

  final Map<String, bool> filters = {
    'Restaurant': true,
    'Hotel': true,
    'Museum': true,
    'Park': true,
    'Shop': true,
  };

  final Map<String, IconData> categoryIcons = {
    'Restaurant': Icons.restaurant,
    'Hotel': Icons.hotel,
    'Museum': Icons.museum,
    'Park': Icons.park,
    'Shop': Icons.shopping_bag,
  };

  final List<Map<String, dynamic>> allPlaces = [
    {'name': 'Bistro A', 'category': 'Restaurant', 'lat': 48.858, 'lng': 2.294},
    {'name': 'Bistro B', 'category': 'Restaurant', 'lat': 48.854, 'lng': 2.333},
    {'name': 'Hotel A', 'category': 'Hotel', 'lat': 48.868, 'lng': 2.328},
    {'name': 'Hotel B', 'category': 'Hotel', 'lat': 48.851, 'lng': 2.327},
    {'name': 'Louvre', 'category': 'Museum', 'lat': 48.861, 'lng': 2.338},
    {'name': 'Orsay', 'category': 'Museum', 'lat': 48.860, 'lng': 2.327},
    {'name': 'Pompidou', 'category': 'Museum', 'lat': 48.861, 'lng': 2.352},
    {'name': 'Luxembourg', 'category': 'Park', 'lat': 48.846, 'lng': 2.337},
    {'name': 'Tuileries', 'category': 'Park', 'lat': 48.863, 'lng': 2.328},
    {'name': 'Galeries Lafayette', 'category': 'Shop', 'lat': 48.874, 'lng': 2.332},
    {'name': 'Le Marais Shops', 'category': 'Shop', 'lat': 48.857, 'lng': 2.362},
  ];

  int _countCategory(String category) {
    return allPlaces.where((p) => p['category'] == category).length;
  }

  @override
  Widget build(BuildContext context) {
    final visible = allPlaces.where((p) => filters[p['category']]!).toList();

    return Scaffold(
      appBar: AppBar(
        title: Text('Toggle Filter List'),
        actions: [
          IconButton(
            icon: Icon(showFilters ? Icons.filter_list_off : Icons.filter_list),
            onPressed: () => setState(() => showFilters = !showFilters),
          ),
          TextButton(
            onPressed: () {
              setState(() {
                filters.updateAll((key, value) => true);
              });
            },
            child: Text('All', style: TextStyle(color: Colors.white)),
          ),
          TextButton(
            onPressed: () {
              setState(() {
                filters.updateAll((key, value) => false);
              });
            },
            child: Text('None', style: TextStyle(color: Colors.white)),
          ),
        ],
      ),
      body: Row(
        children: [
          // Filter panel
          if (showFilters)
            Container(
              width: 180,
              color: Colors.white,
              child: ListView(
                children: filters.keys.map((category) {
                  return CheckboxListTile(
                    value: filters[category],
                    onChanged: (val) {
                      setState(() {
                        filters[category] = val!;
                      });
                    },
                    title: Row(
                      children: [
                        Icon(categoryIcons[category], size: 18),
                        SizedBox(width: 6),
                        Expanded(child: Text(category, style: TextStyle(fontSize: 13))),
                      ],
                    ),
                    subtitle: Text('${_countCategory(category)} places',
                        style: TextStyle(fontSize: 11)),
                    dense: true,
                    controlAffinity: ListTileControlAffinity.leading,
                  );
                }).toList(),
              ),
            ),
          // Map
          Expanded(
            child: MapMetrics(
              styleUrl:
                  'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (MapMetricsController controller) {
                mapController = controller;
              },
              initialCameraPosition: CameraPosition(
                target: LatLng(48.858, 2.335),
                zoom: 13.5,
              ),
              markers: visible.map((place) {
                return Marker(
                  markerId: MarkerId(place['name']),
                  position: LatLng(place['lat'], place['lng']),
                  infoWindow: InfoWindow(
                    title: place['name'],
                    snippet: place['category'],
                  ),
                );
              }).toSet(),
            ),
          ),
        ],
      ),
    );
  }
}

Next Steps


Tip: Use FilterChip for mobile-friendly category toggles, and CheckboxListTile for desktop-style sidebar filters. Combine both with setState() to instantly show/hide markers.