# Map Styles

Last updated:

<!-- -->

October 18, 2026

<!-- -->

\| 5 minutes read

<br />

<br />

This example demonstrates how to create a Flutter app that displays an interactive map with a non-default style using Maps SDK for Flutter.

## How it works[​](#how-it-works "Direct link to How it works")

This example demonstrates the following features:

* Display and manage various map styles dynamically.
* Provide smooth transitions between styles while ensuring a seamless user experience.
* Download online map styles.

![Initial map view](/docs/flutter/assets/images/example_flutter_map_styles1-40fa1c4fd3daeb77be4d1fcabc96f3f0.png)

**Initial map view**

![Map styles page](/docs/flutter/assets/images/example_flutter_map_styles2-d43188d43f9ce1c8125084e97be9edaf.png)

**Map styles page**

![Applied map style](/docs/flutter/assets/images/example_flutter_map_styles3-ca086904b5272fb5e3e6c8989c2f9871.png)

**Applied map style**

### Build the main application[​](#build-the-main-application "Direct link to Build the main application")

Define the main application widget, `MyApp` .

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/map_styles/lib/main.dart?ref_type=heads#L17)

```dart
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Map Styles',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // GemMapController object used to interact with the map
  late GemMapController _mapController;

  @override
  void dispose() {
    GemKit.release();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.deepPurple[900],
        title: const Text('Map Styles', style: TextStyle(color: Colors.white)),
        actions: [
          IconButton(
            onPressed: () async => await _onMapButtonTap(context),
            icon: const Icon(Icons.map_outlined, color: Colors.white),
          ),
        ],
      ),
      body: GemMap(
        key: ValueKey("GemMap"),
        onMapCreated: _onMapCreated,
        appAuthorization: projectApiToken,
      ),
    );
  }

  void _onMapCreated(GemMapController controller) async {
    _mapController = controller;

    SdkSettings.setAllowOffboardServiceOnExtraChargedNetwork(
      ServiceGroupType.contentService,
      true,
    );

    _mapController.registerOnSetMapStyle((styleId, stylePath, viaApi) {
      print("Style updated!");
    });
  }

  Future<void> _onMapButtonTap(BuildContext context) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute<ContentStoreItem>(
        builder: (context) => MapStylesPage(),
      ),
    );

    if (result != null) {
      // Handle the returned data

      // Wait for the map refresh to complete
      await Future<void>.delayed(Duration(milliseconds: 500));

      // Set selected map style
      _mapController.preferences.setMapStyle(result);
    }
  }
}

```

### Map Styles Page[​](#map-styles-page "Direct link to Map Styles Page")

map\_styles\_page.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/map_styles/lib/map_styles_page.dart?ref_type=heads#L13)

```dart
class MapStylesPage extends StatefulWidget {
  const MapStylesPage({super.key});

  @override
  State<MapStylesPage> createState() => _MapStylesPageState();
}

class _MapStylesPageState extends State<MapStylesPage> {
  final stylesList = <ContentStoreItem>[];

  StylesProvider stylesProvider = StylesProvider.instance;

  @override
  Widget build(BuildContext context) {
    final offlineStyles = StylesProvider.getOfflineStyles();
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: true,
        backgroundColor: Colors.deepPurple[900],
        foregroundColor: Colors.white,
        title: Text("Map styles"),
      ),
      body: FutureBuilder<List<ContentStoreItem>?>(
        future: StylesProvider.getOnlineStyles(),
        builder: (context, snapshot) {
          return CustomScrollView(
            slivers: [
              const SliverToBoxAdapter(child: Text("Local: ")),
              SliverList.separated(
                separatorBuilder: (context, index) =>
                    const Divider(indent: 20, height: 0),
                itemCount: offlineStyles.length,
                itemBuilder: (context, index) {
                  final styleItem = offlineStyles.elementAt(index);
                  return Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: StyleItem(
                      styleItem: styleItem,
                      onItemStatusChanged: () {
                        if (mounted) setState(() {});
                      },
                    ),
                  );
                },
              ),
              const SliverToBoxAdapter(child: Text("Online: ")),
              if (snapshot.connectionState == ConnectionState.waiting)
                const SliverToBoxAdapter(
                  child: Center(child: CircularProgressIndicator()),
                )
              else if (snapshot.data == null || snapshot.data!.isEmpty)
                const SliverToBoxAdapter(
                  child: Center(
                    child: Text(
                      'The list of online styles is not available (missing internet connection or expired local content).',
                      textAlign: TextAlign.center,
                    ),
                  ),
                )
              else
                SliverList.separated(
                  itemCount: snapshot.data!.length,
                  separatorBuilder: (context, index) =>
                      const Divider(indent: 50, height: 0),
                  itemBuilder: (context, index) {
                    final styleItem = snapshot.data!.elementAt(index);
                    return Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: StyleItem(
                        styleItem: styleItem,
                        onItemStatusChanged: () {
                          if (mounted) setState(() {});
                        },
                      ),
                    );
                  },
                ),
            ],
          );
        },
      ),
    );
  }
}

class StyleItem extends StatefulWidget {
  final ContentStoreItem styleItem;

  final void Function() onItemStatusChanged;

  const StyleItem({
    super.key,
    required this.styleItem,
    required this.onItemStatusChanged,
  });

  @override
  State<StyleItem> createState() => _StyleItemState();
}

class _StyleItemState extends State<StyleItem> {
  int _downloadProgress = 0;

  @override
  void initState() {
    super.initState();

    final styleItem = widget.styleItem;

    _downloadProgress = styleItem.downloadProgress;

    // If the style is downloading pause and start downloading again
    // so the progress indicator updates value from callback
    if (getIsDownloadingOrWaiting(styleItem)) {
      final errCode = styleItem.pauseDownload();

      if (errCode == GemError.success) {
        Future.delayed(Duration(seconds: 1), () {
          _startStyleDownload(styleItem);
        });
      } else {
        print(
          "Download pause for item ${styleItem.id} failed with code $errCode",
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    final styleItem = widget.styleItem;

    return InkWell(
      onTap: () => _onStyleTap(),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.memory(
            getStyleImage(styleItem, Size(400, 300))!,
            width: 175,
            gaplessPlayback: true,
          ),
          Expanded(
            child: ListTile(
              title: Text(
                maxLines: 5,
                styleItem.name,
                overflow: TextOverflow.ellipsis,
                style: const TextStyle(
                  color: Colors.black,
                  fontSize: 14,
                  fontWeight: FontWeight.w600,
                ),
              ),
              subtitle: Text(
                "${(styleItem.totalSize / (1024.0 * 1024.0)).toStringAsFixed(2)} MB",
                style: const TextStyle(color: Colors.black, fontSize: 16),
              ),
              trailing: SizedBox.square(
                dimension: 50,
                child: Builder(
                  builder: (context) {
                    if (styleItem.isCompleted) {
                      return const Icon(
                        Icons.download_done,
                        color: Colors.green,
                      );
                    } else if (getIsDownloadingOrWaiting(styleItem)) {
                      return SizedBox(
                        height: 10,
                        child: CircularProgressIndicator(
                          value: _downloadProgress.toDouble() / 100,
                          color: Colors.blue,
                          backgroundColor: Colors.grey.shade300,
                        ),
                      );
                    } else if (styleItem.status ==
                        ContentStoreItemStatus.paused) {
                      return const Icon(Icons.pause);
                    }
                    return const SizedBox.shrink();
                  },
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  // Method that downloads the current selected map style
  void _onStyleTap() {
    final item = widget.styleItem;

    if (item.isCompleted) {
      Navigator.of(context).pop(item);
      return;
    }

    if (getIsDownloadingOrWaiting(item)) {
      // Pause the download.
      item.pauseDownload();
      setState(() {});
    } else {
      // Download the map.
      _startStyleDownload(item);
    }
  }

  void _onStyleDownloadProgressUpdated(int progress) {
    if (mounted) {
      setState(() {
        _downloadProgress = progress;
        print('Progress: $progress');
      });
    }
  }

  void _onStyleDownloadFinished(GemError err) {
    widget.onItemStatusChanged();

    // If success, update state
    if (err == GemError.success && mounted) {
      setState(() {});
    }
  }

  void _startStyleDownload(ContentStoreItem styleItem) {
    // Download style
    styleItem.asyncDownload(
      _onStyleDownloadFinished,
      onProgress: _onStyleDownloadProgressUpdated,
      allowChargedNetworks: true,
    );
  }
}

```

### Map Styles Provider[​](#map-styles-provider "Direct link to Map Styles Provider")

styles\_provider.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/map_styles/lib/styles_provider.dart?ref_type=heads#L15)

```dart
class StylesProvider {
  StylesProvider._privateConstructor();
  static final StylesProvider instance = StylesProvider._privateConstructor();

  // Method to load the local-available styles
  static List<ContentStoreItem> getOfflineStyles() {
    final localMaps = ContentStore.getLocalContentList(
      ContentType.viewStyleHighRes,
    );

    final result = <ContentStoreItem>[];

    for (final map in localMaps) {
      if (map.status == ContentStoreItemStatus.completed) {
        result.add(map);
      }
    }

    return result;
  }

  // Method to load the available styles
  static Future<List<ContentStoreItem>> getOnlineStyles() {
    final completer = Completer<List<ContentStoreItem>>();

    ContentStore.asyncGetStoreContentList(ContentType.viewStyleHighRes, (
      err,
      items,
      isCached,
    ) {
      if (err != GemError.success) {
        print("Error while getting styles: ${err.name}");
        return;
      }
      completer.complete(items);
    });

    return completer.future;
  }
}

Uint8List? getStyleImage(ContentStoreItem contentItem, Size? size) =>
    contentItem.imgPreview.getRenderableImageBytes(
      size: size,
      format: ImageFileFormat.png,
    );

bool getIsDownloadingOrWaiting(ContentStoreItem contentItem) => [
  ContentStoreItemStatus.downloadQueued,
  ContentStoreItemStatus.downloadRunning,
  ContentStoreItemStatus.downloadWaitingNetwork,
  ContentStoreItemStatus.downloadWaitingFreeNetwork,
  ContentStoreItemStatus.downloadWaitingNetwork,
].contains(contentItem.status);

```

![](/docs/flutter/assets/images/example_flutter_map_styles4-1b069ee20cce52eb7e25aa748139f3c5.png)

![](/docs/flutter/assets/images/example_flutter_map_styles5-770dd3536d38763384792b67f8070294.png)

![](/docs/flutter/assets/images/example_flutter_map_styles6-d0b8b397170c15bb608325d3f47cbc00.png)

![](/docs/flutter/assets/images/example_flutter_map_styles7-5887b15a58ee8b97d3a9abac27c3b652.png)

![](/docs/flutter/assets/images/example_flutter_map_styles8-fb17165c4a6d1d497606e572b8d9ded4.png)
