Get Started
Contents
Introduction
Welcome! This guide provides a foundational example demonstrating how to integrate Trimble’s Mobile Maps SDK into a Flutter application, specifically targeting the Android platform. Our goal is to help you get a basic map view up and running using Flutter’s PlatformView
mechanism.
Who is this guide for?
This guide is intended for developers who have some basic experience with Flutter development. We assume you are familiar with:
- Creating and running a new Flutter project.
- The general structure of a Flutter application.
- Basic Dart and Flutter concepts.
If you are new to Flutter or need a refresher, we highly recommend starting with the official Flutter ‘Get Started’ guide before proceeding.
What will you learn?
By following this guide, you will learn how to:
- Set up the necessary configurations in your Android Flutter project to use the Trimble Maps SDK.
- Implement a basic Flutter
PlatformView
to display a Trimble Map within your Flutter UI. - Establish the initial connection between your Flutter code and the native Android map view.
Scope and Limitations
Please note that this guide focuses solely on the initial setup and integration for displaying a map on Android using PlatformView
. It serves as a starting point and does not cover:
- iOS integration (currently unsupported by this specific guide).
- The full range of features and capabilities offered by the Trimble Maps SDK (like routing, custom styling, location services integration, etc.).
- Advanced
PlatformView
communication techniques.
For comprehensive information on the SDK’s features and functionalities, this guide should be used in conjunction with the official Trimble Maps SDK for Android documentation.
Let’s get started!
Set Up Dependencies
Before writing any Dart code to display the map, you need to configure the native Android part of your Flutter application to include the Trimble Maps SDK libraries. This involves two main steps: telling Gradle where to find the SDK (adding the repository) and specifying which SDK libraries your app needs (adding the dependencies).
Step 1: Add the Trimble Maps Repository
Gradle needs to know the location of the Trimble Maps Maven repository to download the SDK libraries.
-
Open your project-level Gradle configuration file, located at:
android/build.gradle
-
Locate the
allprojects
block. Inside it, find therepositories
block. -
Add the Trimble Maps Maven repository URL within the
repositories
block. Make sure it’s alongside other repositories likegoogle()
andmavenCentral()
.
// android/build.gradle
allprojects {
repositories {
google()
mavenCentral()
// Add the Trimble Maps Maven repository
maven {
url = uri("https://trimblemaps.jfrog.io/artifactory/android")
}
}
}
Note: The exact structure of your android/build.gradle
might differ slightly, but the key is to add the maven { ... }
block inside allprojects { repositories { ... } }
.
Step 2: Add the SDK Dependencies
Now, specify the Trimble Maps SDK libraries that your application module depends on.
-
Open your app-level Gradle configuration file, located at:
android/app/build.gradle
-
Scroll down to find the
dependencies { ... }
block. -
Add the
implementation
lines for the Trimble Maps Android SDK and Services SDK inside this block.
// android/app/build.gradle
dependencies {
// Other existing Flutter and Android dependencies will be here...
// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // Example
// Add Trimble Maps SDK dependencies here.
implementation("com.trimblemaps.mapsdk:maps-android-sdk:2.0.0")
implementation("com.trimblemaps.mapsdk:maps-sdk-services:1.5.0")
// Other existing test dependencies might be here, e.g.:
// testImplementation 'junit:junit:4.13.2'
// androidTestImplementation 'androidx.test.ext:junit:1.1.5'
}
Important:
- The version numbers (
2.0.0
formaps-android-sdk
and1.5.0
formaps-sdk-services
) provided here are examples. While they should work for this guide, you might want to check the developer portal or the Artifactory repository for the latest released versions for your production applications. - Ensure you add these lines within the main
dependencies { ... }
block, not inside any other specific configuration block unless intended.
Step 3: Sync Gradle Files
After editing the Gradle files, your IDE (like Android Studio or VS Code) will likely show a notification prompting you to sync your project. Click Sync Now (or the equivalent option) to ensure your project recognizes the new repository and dependencies.
Implement the Native Android View (TrimbleMapView.kt)
Now that the dependencies are set up, we need to create the native Android code that will manage and display the Trimble Map. We’ll use Flutter’s PlatformView
interface to embed this native view within our Flutter widget tree.
This involves creating a Kotlin class that:
- Initializes the Trimble Maps SDK and the native
MapView
. - Manages the lifecycle of the
MapView
. - Sets up a
MethodChannel
to receive commands from Flutter. - Executes map actions (like zooming or changing style) based on those commands.
- Returns the actual
MapView
instance to be displayed by Flutter.
Step 1: Create the TrimbleMapView.kt File
First, create a new Kotlin file named TrimbleMapView.kt
within your Android project structure. A common location is:
android/app/src/main/kotlin/com/your_company_name/your_project_name/TrimbleMapView.kt
(Remember to replace com/your_company_name/your_project_name/
with your actual package name structure).
Step 2: Add the TrimbleMapView Class Code
Paste the following Kotlin code into the TrimbleMapView.kt
file you just created. (We’ve added extra logging in here so you can follow the workflow in your debugger.):
import android.content.Context
import android.util.Log
import android.view.View
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
import com.trimblemaps.account.LicensedFeature
import com.trimblemaps.account.TrimbleMapsAccountManager
import com.trimblemaps.account.models.TrimbleMapsAccount
import com.trimblemaps.mapsdk.TrimbleMaps
import com.trimblemaps.mapsdk.camera.CameraPosition
import com.trimblemaps.mapsdk.camera.CameraUpdateFactory
import com.trimblemaps.mapsdk.geometry.LatLng
import com.trimblemaps.mapsdk.maps.MapView
import com.trimblemaps.mapsdk.maps.OnMapReadyCallback
import com.trimblemaps.mapsdk.maps.Style
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding // Needed for lifecycle
/**
* A PlatformView that wraps the Trimble Map SDK's MapView and
* exposes zoom, style, and center controls via a MethodChannel.
* It also handles MapView lifecycle events by observing the host Activity's lifecycle.
*/
class TrimbleMapView(
private val context: Context, // Android context for view creation
messenger: BinaryMessenger, // Flutter BinaryMessenger for channel communication
private val viewId: Int, // Unique identifier for this map view instance
private val apiKey: String // API key for Trimble Maps services
) : PlatformView, DefaultLifecycleObserver { // Implement PlatformView and LifecycleObserver
companion object {
private const val TAG = "TrimbleMapView" // Tag for Android log messages
}
// The core native Trimble MapView instance
private val mapView: MapView
// MethodChannel for communication between Flutter and this native view.
// The channel name includes the viewId to ensure uniqueness if multiple maps are shown.
private val channel: MethodChannel = MethodChannel(
messenger,
"trimble_map_view_$viewId" // Unique channel name
)
// Initialization block executed when a TrimbleMapView instance is created
init {
Log.d(TAG, "Initializing TrimbleMapView for viewId=$viewId")
// 1. Initialize Trimble Maps Account (Authentication)
// Sets up the account using the provided API key and specifies
// that we intend to use the Maps SDK feature.
val account: TrimbleMapsAccount = TrimbleMapsAccount.builder()
.apiKey(apiKey)
.addLicensedFeature(LicensedFeature.MAPS_SDK)
.build()
TrimbleMapsAccountManager.initialize(account)
// IMPORTANT: Wait for initialization to complete before proceeding.
TrimbleMapsAccountManager.awaitInitialization()
Log.d(TAG, "TrimbleMapsAccount initialized")
// 2. Initialize the Native MapView
// Ensures the TrimbleMaps singleton is ready and creates the MapView UI component.
TrimbleMaps.getInstance(context)
mapView = MapView(context)
// 3. Setup Asynchronous Map Ready Callback
// The map needs time to load resources and its style. We use getMapAsync
// to get a callback (`OnMapReadyCallback`) when the map is fully ready.
mapView.getMapAsync(OnMapReadyCallback { trimbleMap ->
Log.d(TAG, "Map ready, applying initial style: MOBILE_DAY")
// Set an initial map style (e.g., MOBILE_DAY)
trimbleMap.setStyle(Style.MOBILE_DAY) { style -> // Callback fires when style is loaded
Log.d(TAG, "Style loaded: ${style.uri}")
// IMPORTANT: Notify Flutter that the map is ready to be interacted with.
try {
channel.invokeMethod("mapReady", null)
Log.d(TAG, "Sent 'mapReady' notification to Flutter")
} catch (e: Exception) {
Log.e(TAG, "Error invoking mapReady on channel", e)
}
}
})
// 4. Set up Method Call Handler
// This listener waits for method calls invoked from the Flutter side using the channel.
channel.setMethodCallHandler { call, result ->
Log.d(TAG, "Received method call from Flutter: ${call.method}")
try {
// Use a 'when' block to handle different method names sent from Flutter
when (call.method) {
"zoomIn" -> {
mapView.getMapAsync { map -> map.easeCamera(CameraUpdateFactory.zoomIn()) }
result.success(null) // Indicate success back to Flutter
Log.d(TAG, "zoomIn executed")
}
"zoomOut" -> {
mapView.getMapAsync { map -> map.easeCamera(CameraUpdateFactory.zoomOut()) }
result.success(null)
Log.d(TAG, "zoomOut executed")
}
"toggleStyle" -> {
mapView.getMapAsync { map ->
// Check the current style and switch to the other
val newStyle = if (map.style?.uri == Style.MOBILE_DAY) Style.MOBILE_NIGHT else Style.MOBILE_DAY
map.setStyle(newStyle)
Log.d(TAG, "toggleStyle executed, set to: ${newStyle}")
}
result.success(null)
}
"center" -> {
// Extract arguments sent from Flutter
val lat = call.argument<Double>("lat") ?: 0.0
val lng = call.argument<Double>("lng") ?: 0.0
Log.d(TAG, "center executing for Lat: $lat, Lng: $lng")
// Build a camera position centered on the coordinates with a fixed zoom
val position = CameraPosition.Builder()
.target(LatLng(lat, lng))
.zoom(8.0) // Example zoom level
.build()
// Animate the map camera to the new position
mapView.getMapAsync { map ->
map.animateCamera(CameraUpdateFactory.newCameraPosition(position))
}
result.success(null)
}
// Handle any methods that aren't recognized
else -> {
Log.w(TAG, "Method not implemented: ${call.method}")
result.notImplemented()
}
}
} catch (e: Exception) {
// Catch potential errors during method handling and report back to Flutter
Log.e(TAG, "Error handling method ${call.method}", e)
result.error("NATIVE_ERROR", "Error executing method ${call.method}: ${e.localizedMessage}", null)
}
}
}
/**
* Attaches this view as an observer to the host Activity's lifecycle.
* This is crucial for forwarding lifecycle events (like onResume, onPause)
* to the underlying MapView, which needs them for proper operation.
*/
fun attachToActivity(binding: ActivityPluginBinding) {
Log.d(TAG, "Attaching to activity lifecycle")
// Cast the activity to LifecycleOwner and add this class as an observer
(binding.activity as? LifecycleOwner)?.lifecycle?.addObserver(this)
}
// --- Lifecycle Event Handling (from DefaultLifecycleObserver) ---
// These methods are called automatically when the host Activity's lifecycle changes.
// We forward these calls directly to the corresponding MapView methods.
override fun onStart(owner: LifecycleOwner) {
Log.d(TAG, "Forwarding onStart to MapView")
mapView.onStart()
}
override fun onResume(owner: LifecycleOwner) {
Log.d(TAG, "Forwarding onResume to MapView")
mapView.onResume()
}
override fun onPause(owner: LifecycleOwner) {
Log.d(TAG, "Forwarding onPause to MapView")
mapView.onPause()
}
override fun onStop(owner: LifecycleOwner) {
Log.d(TAG, "Forwarding onStop to MapView")
mapView.onStop()
}
override fun onDestroy(owner: LifecycleOwner) {
Log.d(TAG, "Forwarding onDestroy to MapView and cleaning up observer")
mapView.onDestroy()
// Optional: Remove observer explicitly, though lifecycle handling usually manages this.
owner.lifecycle.removeObserver(this)
}
// --- PlatformView Implementation ---
/**
* Returns the actual Android View (the MapView instance) that Flutter will embed.
*/
override fun getView(): View = mapView
/**
* Called when the Flutter PlatformView is disposed.
* Essential for cleaning up resources to prevent memory leaks.
*/
override fun dispose() {
Log.d(TAG, "dispose called - Cleaning up MethodChannel and MapView")
// Remove the method call handler to break the communication link.
channel.setMethodCallHandler(null)
// Destroy the MapView to release its resources.
mapView.onDestroy()
// Note: Lifecycle observer cleanup happens in onDestroy generally.
}
}
Step 3: Review the Code
- Class Definition:
TrimbleMapView
implementsPlatformView
(required by Flutter to get the nativeView
) andDefaultLifecycleObserver
(to hook into the Android Activity’s lifecycle).- The constructor receives
context
,messenger
(forMethodChannel
),viewId
(unique ID for this view instance), and yourapiKey
.
mapView: MapView
: This holds the actual instance of the Trimble MapsMapView
widget.channel: MethodChannel
: This is the communication bridge. Note the channel nametrimble_map_view_$viewId
– using theviewId
ensures that if you had multiple map views, each would have its own distinct channel.init { ... }
Block: This is executed whenTrimbleMapView
is created:- Account Initialization: Sets up authentication using your API key and the
.addLicensedFeature(LicensedFeature.MAPS_SDK)
line.awaitInitialization()
is crucial to ensure the account is ready before creating the map. - MapView Initialization: Creates the native
MapView
UI component. getMapAsync { ... }
: This is vital. Map initialization is asynchronous. The code inside this callback runs only after the map is fully loaded and ready.trimbleMap.setStyle(...)
: Sets an initial visual style for the map.channel.invokeMethod("mapReady", null)
: Sends a signal back to Flutter indicating that the native map is ready. This is important so the Flutter side knows it can start sending commands.
channel.setMethodCallHandler { ... }
: This is the listener for incoming messages from Flutter.- The
when (call.method)
block checks the name of the method invoked by Flutter (e.g.,"zoomIn"
,"toggleStyle"
).
- The
- Account Initialization: Sets up authentication using your API key and the
attachToActivity(...)
: This function will be called later (by thePlatformViewFactory
) to link this view’s lifecycle handling to the main Android Activity.- Lifecycle Methods (
onStart
,onResume
, etc.): By implementingDefaultLifecycleObserver
and registering viaattachToActivity
, these methods automatically receive calls when the hosting Android Activity goes through lifecycle changes. It’s critical to forward these calls to themapView
’s corresponding methods (mapView.onStart()
,mapView.onResume()
, etc.) for the map to function correctly (e.g., render updates, save state, release resources). getView(): View
: Implements thePlatformView
requirement, simply returning themapView
instance so Flutter can display it.dispose():
Implements thePlatformView
requirement for cleanup. It removes the method channel handler and, crucially, callsmapView.onDestroy()
to release the map’s resources when the Flutter widget is removed from the tree. This prevents memory leaks.
With this class created, you have the core native component ready. The next step is to work on the TrimbleMapViewFactory
so that Flutter can use it to create instances of this TrimbleMapView
.
Create the PlatformView Factory (TrimbleMapViewFactory.kt)
Flutter needs a way to create instances of the TrimbleMapView
you defined earlier whenever it needs to display the map widget. This is done using a PlatformViewFactory
.
The factory is a relatively simple class whose primary responsibility is to receive creation parameters from Flutter (like your API key) and use them to instantiate and return a new TrimbleMapView
.
Step 1: Create the TrimbleMapViewFactory.kt File
Create a new Kotlin file named TrimbleMapViewFactory.kt
in the same directory as your TrimbleMapView.kt
:
android/app/src/main/kotlin/com/your_company_name/your_project_name/TrimbleMapViewFactory.kt
(Again, replace com/your_company_name/your_project_name/
with your actual package name).
Step 2: Add the TrimbleMapViewFactory Class Code
Paste the following Kotlin code into the TrimbleMapViewFactory.kt
file.
import android.content.Context // Android context for view creation
import io.flutter.plugin.common.BinaryMessenger // Messenger for communicating with Flutter side
import io.flutter.plugin.common.StandardMessageCodec // Codec for encoding/decoding messages
import io.flutter.plugin.platform.PlatformView // Interface for embedding native views in Flutter
import io.flutter.plugin.platform.PlatformViewFactory // Factory for creating PlatformView instances
/**
* Factory responsible for creating instances of TrimbleMapView.
* Extends PlatformViewFactory to integrate with Flutter's
* platform view mechanism using StandardMessageCodec.
*/
class TrimbleMapViewFactory(
private val messenger: BinaryMessenger // BinaryMessenger from Flutter engine for channel setup
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { // Use StandardMessageCodec for argument marshalling
/**
* Called by Flutter when a new platform view is needed.
* @param context Android context for view creation
* @param viewId Unique identifier for this view instance
* @param args Initialization parameters passed from Dart
* @return A new instance of TrimbleMapView
*/
override fun create(
context: Context,
viewId: Int,
args: Any?
): PlatformView {
// Cast the args to a map of parameters (must match what Dart side sends)
val params = args as Map<String, Any>
// Extract the API key for Trimble Maps from initialization params
val apiKey = params["apiKey"] as String
// Create and return the native TrimbleMapView with provided context, messenger, view ID, and API key
return TrimbleMapView(context, messenger, viewId, apiKey)
}
}
Step 3: Review the Code
- Class Definition:
TrimbleMapViewFactory
extends Flutter’sPlatformViewFactory
.- It uses
StandardMessageCodec.INSTANCE
which is the default mechanism Flutter uses to encode theargs
parameter passed during view creation. - The constructor takes a
BinaryMessenger
instance. This is essential because theTrimbleMapView
needs it to set up itsMethodChannel
for communication back to Flutter. TheBinaryMessenger
is typically obtained from the Flutter plugin registrar or engine.
create
Method:- This is the core method implemented from
PlatformViewFactory
. Flutter calls this method automatically whenever your Dart code requests a new instance of the platform view associated with this factory. context: Context
: The Android application context.viewId: Int
: A unique integer ID assigned by Flutter for this specific instance of the platform view. This ID is passed toTrimbleMapView
and used to create a uniqueMethodChannel
name (trimble_map_view_$viewId
).args: Any?
: This parameter holds the data passed from your Dart code when you create theAndroidView
widget. In this example, we expect it to be aMap
containing the API key.- Argument Parsing: The code safely casts
args
toMap<String, Any>
and extracts theapiKey
value associated with the key"apiKey"
. Crucially, the key used here ("apiKey"
) must exactly match the key you will use in your Dart code when providing creation parameters. - Instantiation: Finally, it creates a new
TrimbleMapView
instance, passing the requiredcontext
, themessenger
(received in the factory’s constructor), the uniqueviewId
, and the extractedapiKey
. This newTrimbleMapView
object is then returned to Flutter, which handles embedding its view (mapView.getView()
) into the Flutter UI.
- This is the core method implemented from
With the TrimbleMapView
and its TrimbleMapViewFactory
created, the next step is to register this factory with the Flutter engine so that Flutter knows how to create your native map view when requested from Dart.
Register the PlatformView Factory
Now that you have the native view (TrimbleMapView
) and its factory (TrimbleMapViewFactory
), you need to tell Flutter about the factory so it can use it. This registration happens in your app’s main Android Activity, typically MainActivity.kt
.
By registering the factory, you associate a unique string identifier (the “view type”) with your factory class. When Flutter encounters this identifier in your Dart code (within an AndroidView
widget), it will know to ask your TrimbleMapViewFactory
to create the native view.
Step 1: Locate and Modify MainActivity.kt
Open your main activity file, usually located at:
android/app/src/main/kotlin/com/your_company_name/your_project_name/MainActivity.kt
Modify the configureFlutterEngine
method to register your factory. If the method doesn’t exist, you’ll need to override it.
Step 2: Add the Factory Registration Code
Update your MainActivity.kt
to include the following registration logic within the configureFlutterEngine
method:
// Keep existing imports like:
// import android.os.Bundle
// import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BinaryMessenger
/**
* MainActivity sets up the Flutter engine and registers the TrimbleMapViewFactory
* so that the native map view can be embedded within Flutter UI.
*/
class MainActivity : FlutterActivity() {
/**
* Called when the FlutterEngine is attached to this Activity.
* Use this hook to register platform-specific plugins or view factories.
*/
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 1. Obtain the BinaryMessenger
// This is needed by the factory to pass down to the TrimbleMapView
// for setting up the MethodChannel communication.
val messenger: BinaryMessenger = flutterEngine.dartExecutor.binaryMessenger
// 2. Register the TrimbleMapViewFactory
// Associates the unique string "trimble_map_view" with your factory instance.
// Flutter will use this identifier from the Dart side to request the view.
flutterEngine
.platformViewsController
.registry
.registerViewFactory(
"trimble_map_view", // <-- The View Type ID (used in Dart)
TrimbleMapViewFactory(messenger) // <-- Instance of your factory
)
}
}
Step 3: Review the Code
configureFlutterEngine
Override: This method is the standard entry point provided byFlutterActivity
for interacting with theFlutterEngine
during setup.- Getting
BinaryMessenger
: We retrieve theBinaryMessenger
from theflutterEngine.dartExecutor
. This messenger is crucial for enabling communication (MethodChannels) between Dart and native code. It’s passed to theTrimbleMapViewFactory
constructor. registerViewFactory
Call:- This is the core of the registration. It’s accessed through the engine’s
platformViewsController.registry
. "trimble_map_view"
: This is the view type identifier. It’s a unique string that you define. You must use this exact same string in your Dart code when creating theAndroidView
widget later. This is how Flutter knows which native view to render.TrimbleMapViewFactory(messenger)
: An instance of the factory class you created earlier is passed here. Themessenger
is provided to its constructor.
- This is the core of the registration. It’s accessed through the engine’s
With the factory registered, the Android native side is now fully configured. The next steps involve writing the Dart code in your Flutter application to display this native view and communicate with it.
Implement the Flutter UI (main.dart)
With the native Android components set up and registered, we can now write the Flutter code to display the map and add interactive controls.
This involves:
- Setting up the main application structure.
- Using the
AndroidView
widget to embed the native view, identified by theviewType
string we registered earlier. - Passing the necessary API key to the native side during view creation.
- Establishing a
MethodChannel
to communicate between Dart and the nativeTrimbleMapView
. - Handling the
mapReady
callback from native code to know when the map is initialized. - Creating UI buttons that invoke methods (like
zoomIn
,toggleStyle
) on the native map view via theMethodChannel
.
Step 1: Locate main.dart
This code typically resides in the main entry point of your Flutter application:
lib/main.dart
Step 2: Add the Flutter Application Code
Replace the contents of your lib/main.dart
with the following code:
import 'package:flutter/foundation.dart'; // Provides defaultTargetPlatform
import 'package:flutter/material.dart'; // Material widgets and theming
import 'package:flutter/services.dart'; // MethodChannel and message codecs
void main() {
// Ensure Flutter framework is fully initialized before creating the app
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp()); // Launch the app
}
/// Root widget of the application
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
// IMPORTANT: Replace 'YOUR_API_KEY' with your actual Trimble Maps API key
home: MapWithControls(apiKey: 'YOUR_API_KEY'),
);
}
}
/// Stateful widget that hosts the native Trimble map and control buttons
class MapWithControls extends StatefulWidget {
final String apiKey; // API Key passed from MyApp
const MapWithControls({Key? key, required this.apiKey}) : super(key: key);
@override
State<MapWithControls> createState() => _MapWithControlsState();
}
class _MapWithControlsState extends State<MapWithControls> {
// Channel for communicating Dart -> Native (TrimbleMapView)
late MethodChannel _channel;
// Flag to track if the native map has finished loading its initial style
bool _mapReady = false;
/// Callback triggered by AndroidView widget AFTER the native view is created.
void _onPlatformViewCreated(int id) {
debugPrint('Native PlatformView created with id: $id');
// Initialize the MethodChannel.
// IMPORTANT: The channel name MUST match the one used in TrimbleMapView.kt
_channel = MethodChannel('trimble_map_view_$id');
// Set up a handler for messages FROM Native TO Dart.
_channel.setMethodCallHandler((call) async {
debugPrint('Dart received method call: ${call.method}');
// Check if the native side signaled that the map is ready
if (call.method == 'mapReady') {
debugPrint('mapReady received from native, enabling controls.');
// Update state to enable UI controls that interact with the map
setState(() {
_mapReady = true;
});
}
// Handle other potential methods from native if needed
});
}
@override
Widget build(BuildContext context) {
// This example uses AndroidView, so restrict to Android platform
if (defaultTargetPlatform != TargetPlatform.android) {
return const Scaffold(
body: Center(child: Text('This example currently supports Android only.')),
);
}
// Main UI structure
return Scaffold(
appBar: AppBar(title: const Text('Trimble Map Flutter Example')),
body: Stack( // Use Stack to overlay controls on top of the map
children: [
// The widget that embeds the native Android view
AndroidView(
// IMPORTANT: Must match the viewType registered in MainActivity.kt
viewType: 'trimble_map_view',
// Callback function called when the native view is created
onPlatformViewCreated: _onPlatformViewCreated,
// Parameters passed to the native PlatformViewFactory's 'create' method
creationParams: <String, dynamic>{
// IMPORTANT: Key 'apiKey' must match the key expected in TrimbleMapViewFactory.kt
'apiKey': widget.apiKey,
},
// Codec used for encoding 'creationParams', must match the factory
creationParamsCodec: const StandardMessageCodec(),
),
// Overlay container for control buttons
Positioned(
bottom: 20,
left: 20,
right: 20, // Allow buttons to space out if needed
child: Card( // Wrap buttons in a Card for better visibility
elevation: 4.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap( // Use Wrap for button layout flexibility
spacing: 8.0, // Horizontal space between buttons
runSpacing: 4.0, // Vertical space if buttons wrap
alignment: WrapAlignment.center,
children: [
// --- Map Control Buttons ---
ElevatedButton(
// Enable button only when _mapReady is true
onPressed: _mapReady ? _zoomIn : null,
child: const Text('Zoom In'),
),
ElevatedButton(
onPressed: _mapReady ? _zoomOut : null,
child: const Text('Zoom Out'),
),
ElevatedButton(
onPressed: _mapReady ? _toggleStyle : null,
child: const Text('Toggle Style'),
),
ElevatedButton(
onPressed: _mapReady ? _centerMap : null,
child: const Text('Center (CA)'),
),
],
),
),
),
),
],
),
);
}
// --- Button Action Methods ---
void _zoomIn() {
debugPrint('Button: Zoom In');
// Invoke the 'zoomIn' method on the native side via the channel
_channel.invokeMethod('zoomIn');
}
void _zoomOut() {
debugPrint('Button: Zoom Out');
// Invoke the 'zoomOut' method on the native side
_channel.invokeMethod('zoomOut');
}
void _toggleStyle() {
debugPrint('Button: Toggle Style');
// Invoke the 'toggleStyle' method on the native side
_channel.invokeMethod('toggleStyle');
}
void _centerMap() {
// Define coordinates (Example: somewhere in California)
const double lat = 36.7783;
const double lng = -119.4179;
debugPrint('Button: Center Map ($lat, $lng)');
// Invoke the 'center' method on the native side, passing arguments
_channel.invokeMethod('center', <String, double>{
'lat': lat,
'lng': lng,
});
}
}
Summary and Next Steps
Congratulations! You have successfully integrated a basic Trimble Map view into a Flutter application for Android using PlatformView
.
In this guide, we covered the essential steps:
- Configured Dependencies: Added the Trimble Maps Maven repository and SDK dependencies to the Android project’s Gradle files (
android/build.gradle
andandroid/app/build.gradle
). - Implemented Native View: Created the
TrimbleMapView.kt
class, which implementsPlatformView
, initializes the native TrimbleMapView
, handles its lifecycle, and sets up aMethodChannel
for communication. - Created View Factory: Implemented
TrimbleMapViewFactory.kt
to enable Flutter to create instances of the nativeTrimbleMapView
, passing the API key during creation. - Registered Factory: Updated
MainActivity.kt
to register theTrimbleMapViewFactory
with the Flutter engine using a unique view type identifier ("trimble_map_view"
). - Built Flutter UI: Developed the Dart code in
lib/main.dart
using theAndroidView
widget to embed the native map. - Established Communication: Set up the
MethodChannel
on both the Dart and native sides to:- Send commands (zoom, style change, center) from Flutter buttons to the native map.
- Receive a
mapReady
signal from the native map to enable Flutter UI controls.
Get Support
If you encounter issues specifically related to the Trimble Maps SDK, this integration guide, or have further questions about using our SDK features, please don’t hesitate to reach out to our support team:
- Email: support@trimblemaps.com
Further Reading
This guide focused on the fundamental setup for displaying a map using Flutter’s PlatformView
on Android. The Trimble Maps SDK for Android offers a wide range of additional features not covered here, such as:
- Displaying user location
- Routing
- Advanced camera controls
- Custom map styling
- Displaying custom data
To explore these features and gain a deeper understanding of the underlying native SDK capabilities, please refer to the comprehensive Trimble Maps SDK for Android - Getting Started Guide.
We encourage you to use the foundation built in this guide as a starting point for integrating more advanced map functionalities into your Flutter applications.
Happy mapping!