Skip to content

Latest commit

 

History

History
403 lines (299 loc) · 10.2 KB

File metadata and controls

403 lines (299 loc) · 10.2 KB

Building a Flutter Map Application with Magic Lane SDK

This tutorial walks you through building a Flutter application that integrates the Magic Lane Maps SDK to display an interactive map. The application demonstrates proper SDK integration and state management with Provider.

Map Display Demo

Table of Contents

Prerequisites

  • Flutter SDK (^3.9.2)
  • Dart development environment
  • Magic Lane API token (see Getting Started Guide)
  • IDE with Flutter support (VS Code, Android Studio, etc.)

Project Setup

1. Create a New Flutter Project

flutter create flutter_test_demo
cd flutter_test_demo

2. Dependencies Configuration

Add the required dependencies to your pubspec.yaml:

name: flutter_test_demo
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0

environment:
  sdk: ^3.9.2

dependencies:
  flutter:
    sdk: flutter
  magiclane_maps_flutter: ^3.1.2
  provider: ^6.1.5+1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

flutter:
  uses-material-design: true

Note

It is recommended to use the latest magiclane_maps_flutter package version available.

3. Install Dependencies

flutter pub get

Architecture Overview

The application follows a clear separation of concerns with the following structure:

lib/
├── main.dart                    # Entry point and main app configuration
├── providers/
│   ├── app_providers.dart       # Provider configuration
│   └── gem_map_provider.dart    # Map controller state management
└── widgets/
    └── app_map.dart             # Map widget implementation

Integrate the SDK

Follow these ordered steps from the official Magic Lane Maps SDK for Flutter documentation to integrate the SDK and make the map display work correctly.

You can check for the latest version on pub.dev.

  1. Android native configuration

Update the Android project build.gradle.kts to include the Magic Lane Maven repository and ensure release builds do not shrink resources (the SDK requires native assets to remain available):

Add the Maven repository (e.g., in android/build.gradle.kts or android/build.gradle):

allprojects {
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://developer.magiclane.com/packages/android")
        }
    }
}

And in app/build.gradle.kts ensure release build types don't minify/shrink resources (adjust signing as needed):

buildTypes {
    release {
        signingConfig = signingConfigs.getByName("debug")
        isMinifyEnabled = false
        isShrinkResources = false
    }
}
  1. iOS native configuration
  • Open the iOS Xcode project and set the minimum deployment target to iOS 14.0 or higher.

Runner Build Settings

  • Ensure the Info.plist contains the required development region key to avoid release-mode crashes:
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>

If you modify platform-specific files, run the usual Flutter platform setup commands:

flutter clean
flutter pub get

Implementation

1. State Management Setup

First, create the provider for managing the map controller state:

lib/providers/gem_map_provider.dart

import 'package:flutter/material.dart' hide Route; // Avoid name conflict with Magic Lane SDK Route class
import 'package:magiclane_maps_flutter/magiclane_maps_flutter.dart';

// Provider for managing the GemMapController instance
class GemMapProvider extends ChangeNotifier {
  GemMapController? _controller;

  GemMapController? get controller => _controller;

  void setController(GemMapController controller) {
    _controller = controller;
    notifyListeners();
  }

  void clearController() {
    _controller = null;
    notifyListeners();
  }
}

Key Points:

  • Extends ChangeNotifier for reactive state management
  • Manages the GemMapController instance lifecycle
  • Provides methods to set and clear the controller
  • Notifies listeners when the controller state changes

lib/providers/app_providers.dart

class AppProviders extends StatelessWidget {
  final Widget child;

  const AppProviders({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [ChangeNotifierProvider(create: (_) => GemMapProvider())],
      child: child,
    );
  }
}

Key Points:

  • Wraps the app with MultiProvider for dependency injection
  • Creates and provides the GemMapProvider instance
  • Allows any child widget to access the map provider

2. Map Widget Implementation

lib/widgets/app_map.dart

import 'package:flutter/material.dart';
import 'package:magiclane_maps_flutter/magiclane_maps_flutter.dart';
import 'package:provider/provider.dart';

class AppMap extends StatefulWidget {
  final String appAuthorization;
  const AppMap({super.key, required this.appAuthorization});

  @override
  State<AppMap> createState() => _AppMapState();
}

class _AppMapState extends State<AppMap> {
  void _onMapCreated(GemMapController controller) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final provider = context.read<GemMapProvider>();
      provider.setController(controller);
    });
    setState(() {});
  }

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

  @override
  Widget build(BuildContext context) {
    return GemMap(onMapCreated: _onMapCreated, appAuthorization: widget.appAuthorization);
  }
}

Key Points:

  • StatefulWidget to manage map lifecycle
  • GemMap widget represents the displayed map
  • _onMapCreated callback handles controller initialization
  • Uses WidgetsBinding.instance.addPostFrameCallback for safe provider access
  • Properly releases GemKit resources in dispose()
  • Authorization token for prioritized map services

3. Main Application Setup

lib/main.dart

void main() {
  runApp(const MainApp());
}

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

  /// Global navigator key for accessing navigation context from anywhere in the app
  static final navigatorKey = GlobalKey<NavigatorState>();

  /// Project API token for map services authentication
  static const projectApiToken = String.fromEnvironment('GEM_TOKEN');

  @override
  Widget build(BuildContext context) {
    return AppProviders(
      child: MaterialApp(navigatorKey: navigatorKey, home: const MapPage()),
    );
  }
}

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

  @override
  State<MapPage> createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Stack(children: [AppMap(appAuthorization: MainApp.projectApiToken)]),
    );
  }
}

Key Points:

  • Uses String.fromEnvironment('GEM_TOKEN') for secure token management
  • Wraps the app with AppProviders for state management
  • Global navigator key for navigation access
  • Scaffold with Stack layout for potential overlay elements
  • resizeToAvoidBottomInset: false prevents keyboard interference

SDK Integration Details

Authentication

The Magic Lane SDK requires an API token for authentication:

static const projectApiToken = String.fromEnvironment('GEM_TOKEN');

This approach uses environment variables for security. Set the token when running:

flutter run --dart-define=GEM_TOKEN=your_api_token_here

Map Controller Management

The GemMapController is the primary interface for map interactions:

void _onMapCreated(GemMapController controller) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    final provider = context.read<GemMapProvider>();
    provider.setController(controller);
  });
  setState(() {});
}

Why addPostFrameCallback?

  • Ensures the widget tree is fully built before accessing providers
  • Prevents potential context access issues during widget creation
  • Guarantees safe provider operations

Resource Management

Proper cleanup is essential for preventing memory leaks:

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

Running the Application

1. Set Environment Variable

export GEM_TOKEN="your_magic_lane_api_token"

2. Run the Application

flutter run --dart-define=GEM_TOKEN=$GEM_TOKEN

Or for specific platforms:

# iOS
flutter run -d ios --dart-define=GEM_TOKEN=$GEM_TOKEN

# Android
flutter run -d android --dart-define=GEM_TOKEN=$GEM_TOKEN

If everything is set up correctly, the app will display a responsive, interactive map that supports common gestures — panning, pinch-to-zoom, double-tap zoom, and two-finger shove/rotate interactions — providing a smooth and natural user experience.

Troubleshooting

Common Issues

  1. Watermark displayed over map: Verify API token is correctly set
  2. Build failures: Ensure all dependencies are properly installed
  3. Performance issues: Check resource disposal in dispose methods

Debug Commands

# Check dependencies
flutter pub deps

# Clean and rebuild
flutter clean
flutter pub get
flutter run

# Check for issues
flutter doctor

Conclusion

This tutorial demonstrates how to integrate the Magic Lane Maps SDK into a Flutter application with proper architecture and best practices. The implementation provides a solid foundation for building more complex mapping applications with additional features and functionality.