Map Themes Plugin

A Flutter plugin that provides easy-to-use map theming capabilities for Google Maps and other map implementations. Transform your maps with beautiful predefined themes or create custom styling with minimal code.

Pub Version Flutter License

UI Shots

Features

5+ Built-in Themes: Standard, Dark, Night, Night Blue, and Retro themes
Easy Integration: Simple API with minimal setup required
Auto Persistence: Automatically saves and restores user's theme preference
Three Usage Patterns: Choose from MapThemeWidget, ThemeSelectorWidget, or MapThemeManager
Customizable UI: Dropdown or horizontal list layouts with custom styling
Highly Testable: Clean architecture with comprehensive test coverage
Flexible: Works with any map widget that accepts style JSON Currently supporting **Google maps** at the moment

Demo Video

map_themes

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  map_themes: ^0.0.1
  google_maps_flutter: ^2.5.0 # or your preferred map package [Only google_maps_flutter at the moment]

Then run:

flutter pub get

Platform Setup

For Google Maps integration, follow the official setup guide:

Android (android/app/src/main/AndroidManifest.xml):

<meta-data android:name="com.google.android.geo.API_KEY"
           android:value="YOUR_API_KEY"/>

iOS (ios/Runner/AppDelegate.swift):

GMSServices.provideAPIKey("YOUR_API_KEY")

Usage

Quick Start

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:map_themes/map_themes.dart';

class MapScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MapThemeWidget(
        builder: (mapStyle) {
          return GoogleMap(
            initialCameraPosition: CameraPosition(
              target: LatLng(37.4219983, -122.084),
              zoom: 14.0,
            ),
            style: mapStyle.isEmpty ? null : mapStyle,
          );
        },
      ),
    );
  }
}

Three Usage Patterns

1. MapThemeWidget (All-in-One)

Perfect for quick integration with built-in theme selector:

MapThemeWidget(
  builder: (mapStyle) {
    return GoogleMap(
      initialCameraPosition: _initialPosition,
      style: mapStyle.isEmpty ? null : mapStyle,
      // ... other properties
    );
  },
  showSelector: true,
  selectorAlignment: Alignment.topRight,
  selectorLayout: ThemeSelectorLayout.horizontalList,
)

2. ThemeSelectorWidget (Separate Selector)

When you want full control over map and selector placement:

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  String _currentMapStyle = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Map Themes'),
        actions: [
          ThemeSelectorWidget(
            layout: ThemeSelectorLayout.dropdown,
            onThemeChanged: (themeJson) async {
              setState(() {
                _currentMapStyle = themeJson;
              });
            },
          ),
        ],
      ),
      body: GoogleMap(
        initialCameraPosition: _initialPosition,
        style: _currentMapStyle.isEmpty ? null : _currentMapStyle,
        // ... other properties
      ),
    );
  }
}

3. MapThemeManager (Programmatic Control)

For advanced scenarios requiring direct theme management:

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  final MapThemeManager _themeManager = MapThemeManager();

  @override
  void initState() {
    super.initState();
    _themeManager.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListenableBuilder(
        listenable: _themeManager,
        builder: (context, _) {
          return GoogleMap(
            initialCameraPosition: _initialPosition,
            style: _themeManager.currentStyleJson,
            // ... other properties
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _themeManager.setTheme(MapStyleTheme.dark),
        child: Icon(Icons.palette),
      ),
    );
  }

  @override
  void dispose() {
    _themeManager.dispose();
    super.dispose();
  }
}

Examples

Custom Asset Themes

Load your own custom theme JSON files:

ThemeSelectorWidget(
  customAssetPaths: [
    'assets/themes/custom_blue.json',
    'assets/themes/custom_green.json',
  ],
  onThemeChanged: (themeJson) async {
    // Handle theme change
  },
)

Custom Theme Selector UI

Create completely custom selector UI:

ThemeSelectorWidget(
  customBuilder: ({
    required BuildContext context,
    required MapStyleTheme? currentTheme,
    required String? currentThemeName,
    required List<String> allThemes,
    required Function(String) onThemeSelected,
    required bool isEnabled,
    required ThemeSelectorStyle style,
  }) {
    return Wrap(
      children: allThemes.map((theme) {
        final isSelected = theme == currentThemeName;
        return GestureDetector(
          onTap: () => onThemeSelected(theme),
          child: Container(
            padding: EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: isSelected ? Colors.blue : Colors.grey[300],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(
              theme.toUpperCase(),
              style: TextStyle(
                color: isSelected ? Colors.white : Colors.black,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        );
      }).toList(),
    );
  },
  onThemeChanged: (themeJson) {
    // Handle theme change
  },
)

Shared Theme Manager

Share one theme manager across multiple map widgets:

class MultiMapScreen extends StatefulWidget {
  @override
  _MultiMapScreenState createState() => _MultiMapScreenState();
}

class _MultiMapScreenState extends State<MultiMapScreen> {
  final MapThemeManager _sharedManager = MapThemeManager();

  @override
  void initState() {
    super.initState();
    _sharedManager.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Theme selector
        ThemeSelectorWidget(
          themeManager: _sharedManager,
          onThemeChanged: (style) {}, // Optional callback
        ),

        // Multiple maps sharing the same theme
        Expanded(
          child: MapThemeWidget(
            themeManager: _sharedManager,
            showSelector: false,
            builder: (style) => GoogleMap(/* ... */),
          ),
        ),
        Expanded(
          child: MapThemeWidget(
            themeManager: _sharedManager,
            showSelector: false,
            builder: (style) => GoogleMap(/* ... */),
          ),
        ),
      ],
    );
  }

  @override
  void dispose() {
    _sharedManager.dispose();
    super.dispose();
  }
}

Customization

Theme Selector Styling

Customize the appearance of theme selectors:

ThemeSelectorWidget(
  style: ThemeSelectorStyle(
    backgroundColor: Colors.white,
    selectedBackgroundColor: Colors.blue,
    textColor: Colors.black,
    selectedTextColor: Colors.white,
    borderRadius: 12.0,
    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    margin: EdgeInsets.all(4),
    elevation: 2.0,
  ),
  layout: ThemeSelectorLayout.horizontalList,
  onThemeChanged: (style) {
    // Handle theme change
  },
)

Map Widget Selector Positioning

Control where the theme selector appears on your map:

MapThemeWidget(
  builder: (style) => GoogleMap(/* ... */),
  selectorAlignment: Alignment.bottomLeft,
  selectorBackgroundDecoration: BoxDecoration(
    color: Colors.black.withOpacity(0.7),
    borderRadius: BorderRadius.circular(8),
    boxShadow: [
      BoxShadow(
        color: Colors.black26,
        blurRadius: 4,
        offset: Offset(0, 2),
      ),
    ],
  ),
)

Parameters

MapThemeWidget Parameters

Parameter Type Default Description
builder Widget Function(String) Required Builder function that receives map style JSON
showSelector bool true Whether to show the theme selector overlay
selectorAlignment AlignmentGeometry Alignment.topRight Position of the theme selector on the map
selectorLayout ThemeSelectorLayout horizontalList Layout style for the theme selector
selectorStyle ThemeSelectorStyle? null Custom styling for the theme selector
themeManager MapThemeManager? null External theme manager instance
onThemeChanged Function(String)? null Callback when theme changes
selectorBackgroundDecoration BoxDecoration? null Custom background decoration for selector

ThemeSelectorWidget Parameters

Parameter Type Default Description
onThemeChanged Future<void> Function(String) Required Callback when theme is selected
layout ThemeSelectorLayout dropdown Layout style (dropdown or horizontalList)
style ThemeSelectorStyle? null Custom styling for the selector
showLabels bool true Whether to show theme names
enabled bool true Whether the selector is interactive
themeManager MapThemeManager? null External theme manager instance
customBuilder CustomBuilder? null Custom builder for complete UI control
customAssetPaths List<String>? null Additional custom theme asset paths

MapThemeManager Properties

Property Type Description
currentTheme MapStyleTheme Currently selected predefined theme
currentStyleJson String Current map style JSON string
currentThemeName String? Name of current theme (including custom)
isInitialized bool Whether the manager has been initialized
isLoading bool Whether a theme change operation is in progress
error String? Current error message, null if no error
allThemes Map<String, String> All available themes (name -> asset path)

MapThemeManager Methods

Method Parameters Returns Description
initialize() customAssetPaths: List<String>? Future<void> Initialize the manager and load themes
setTheme() theme: String or MapStyleTheme Future<void> Change to specified theme
dispose() - void Clean up resources

ThemeSelectorStyle Properties

Property Type Default Description
backgroundColor Color Colors.grey[200] Background color for unselected items
selectedBackgroundColor Color Colors.blue Background color for selected item
textColor Color Colors.black Text color for unselected items
selectedTextColor Color Colors.white Text color for selected item
borderRadius double 8.0 Border radius for selector items
padding EdgeInsets EdgeInsets.all(8.0) Internal padding for items
margin EdgeInsets EdgeInsets.all(2.0) External margin for items
elevation double 1.0 Shadow elevation for items

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

  1. Fork and Clone

    git clone https://github.com/Captured-Heart/map_themes.git
    cd map_themes
    
  2. Install Dependencies

    flutter pub get
    cd example && flutter pub get
    
  3. Run Tests

    flutter test
    
  4. Run Example

    cd example
    flutter run
    

Contributing Guidelines

  • Code Style: Follow Dart's official style guide and use flutter format
  • Testing: Maintain test coverage above 90% - add tests for new features
  • Documentation: Update documentation for API changes
  • Commit Messages: Use conventional commits (feat:, fix:, docs:, etc.)

Also, look at our Contributing guidelines

Reporting Issues

When reposting an issue, provide us with additional information such as:

  • Flutter version and platform
  • Minimal reproduction code
  • Expected vs actual behavior
  • Relevant error messages or logs

License

This project is licensed under the MIT License - see the LICENSE file for details.


More Information

Built with 💜 by Nkpozi Marcel Kelechi (X: @Captured-Heart)

Libraries

map_themes
Dynamic Map Themes Plugin