Google Maps integration in a Compose Multiplatform project allows developers to create cross-platform applications with powerful map functionalities. In this guide, we’ll walk through the integration process step-by-step, including sample code snippets for better understanding.
1. Adding Dependencies
Add the required dependencies for Google Maps. Depending on the platforms you’re targeting, include the following in your files:
For Android
in composeApp
Include the following dependency in your build.gradle.kts file:
sourceSets {
androidMain.dependencies {
...// Other dependencies
implementation("com.google.maps.android:maps-compose:6.1.0")
}
}
Note: Google Maps Compose library has been added for Android native.
For iOS
We’ll use Swift Package Manager to add dependencies on the iOS native side.
in iosApp
Open the iosApp.xcodeproj file with Xcode.
Select File -> Add Package Dependencies
Paste the following URL into the search field in the top right.
Google Maps package URL: https://github.com/googlemaps/ios-maps-sdk
Select Exact Version as the Dependency Rule and enter the latest version. Then continue by clicking Add Package.
Note: For new projects it is recommended to choose Exact Version.
Note: Google Maps library has been added for iOS native.
2. API Key Configuration
You need an API key for Google Maps.
Go to the Google Cloud Console. Select your project or create a new one. Open the APIs & Services section. Click on the Enable APIs and Services button at the top. Then enable the Maps SDK for Android and Maps SDK for iOS. Open the Google Maps Platform product. Generate an API key from the Keys & Credentials section.
For Android
in androidMain
Add the key to your AndroidManifest.xml
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY" />
For iOS
in iosApp
Configure your key in iOSApp.swift
import SwiftUI
import GoogleMaps
@main
struct iOSApp: App {
init() {
GMSServices.provideAPIKey("YOUR_API_KEY")
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
3. Building the MapView
Now let’s create a simple map UI in Compose.
in commonMain
Let’s create a data class that holds latitude and longitude double values.
data class Coordinate(
val latitude: Double,
val longitude: Double
)
The following code snippet introduces a cross-platform MapView composable function in Compose Multiplatform. Using the expect keyword, it defines a shared interface for rendering a map, where the actual implementation will differ depending on the platform (e.g., Android or iOS). The function takes a list of Coordinate objects as input, which can be used to display markers on the map. This approach ensures a unified API for map rendering while leveraging platform-specific capabilities:
import androidx.compose.runtime.Composable
@Composable
expect fun MapView(
modifier: Modifier = Modifier,
coordinates: List<Coordinate>,
)
Now we can write our platform specific actual functions.
For Android
in androidMain
Let’s create a MapView.kt file.
Following code defines the Android-specific implementation of the MapView composable, leveraging Google Maps in Compose. It displays a map that dynamically adjusts its camera to fit all provided coordinates using LatLngBounds. For each coordinate, a marker is added to the map with a customizable title. The createBounds helper function calculates the bounds for all markers, ensuring they are visible.
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.rememberMarkerState
@Composable
actual fun MapView(
modifier: Modifier,
coordinates: List<Coordinate>,
) {
val cameraPositionState = rememberCameraPositionState()
val bounds = createBounds(coordinates)
LaunchedEffect(Unit) {
cameraPositionState.move(
update = CameraUpdateFactory.newLatLngBounds(bounds, 100)
)
}
Box(
modifier = modifier
) {
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
) {
coordinates.map {
val markerState = rememberMarkerState(position = LatLng(it.latitude, it.longitude))
Marker(
state = markerState,
title = "Android Native Marker",
)
}
}
}
}
private fun createBounds(coordinates: List<Coordinate>): LatLngBounds {
val boundsBuilder = LatLngBounds.builder()
coordinates.forEach {
boundsBuilder.include(LatLng(it.latitude, it.longitude))
}
return boundsBuilder.build()
}
Our integration on the Android side is now complete! Next, we’ll focus on the iOS native side.
For iOS
in iosMain
Let’s edit the MainViewController.kt file as in the code snippet.
Following code sets up the iOS-specific integration for a Compose Multiplatform application. It defines a MainViewController function that links a native iOS UIViewController with a Jetpack Compose UI using ComposeUIViewController. The mapViewController is initialized as a lambda to render a map view based on a list of coordinates. This approach ensures seamless communication between the Compose UI and native iOS components, enabling shared functionality across platforms.
import androidx.compose.ui.window.ComposeUIViewController
import platform.UIKit.UIViewController
lateinit var mapViewController: (List<Coordinate>) -> UIViewController
fun MainViewController(
mapUIViewController: (List<Coordinate>) -> UIViewController
) = ComposeUIViewController {
mapViewController = mapUIViewController
App()
}
Next, let’s create a file named MapView.kt.
Following code provides the iOS-specific implementation of the MapView composable, using UIKitView to integrate native iOS views within Compose. The UIKitView creates a map view by invoking the mapViewController with the provided coordinates and embeds it into the Compose layout. The UIKitInteropProperties are configured to enable native accessibility and set the interaction mode to NonCooperative, ensuring smooth integration between Compose and UIKit components. This setup allows seamless rendering of native map views while maintaining compatibility with Compose UI elements.
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.UIKitInteropInteractionMode
import androidx.compose.ui.viewinterop.UIKitInteropProperties
import androidx.compose.ui.viewinterop.UIKitView
@OptIn(ExperimentalComposeUiApi::class)
@Composable
actual fun MapView(
modifier: Modifier,
coordinates: List<Coordinate>,
) {
UIKitView(
factory = { mapViewController.invoke(coordinates).view },
modifier = modifier,
properties = UIKitInteropProperties(
isNativeAccessibilityEnabled = true,
interactionMode = UIKitInteropInteractionMode.NonCooperative,
)
)
}
in iosApp
Open the iosApp.xcodeproj file with Xcode.
Note: Make all changes to the iosApp module in Xcode. Making changes in a different IDE can cause various problems.
Create a MapView.swift file.
Following code defines the MapView struct for iOS, implementing the UIViewRepresentable protocol to integrate a native GMSMapView (Google Maps) into SwiftUI. The makeUIView function initializes the map and adds markers for each coordinate, displaying their locations with titles. It also uses a GMSMutablePath to calculate the map’s bounds and adjusts the camera to fit all markers with padding. The updateUIView function is included for future updates but is left empty here. This setup enables seamless integration of Google Maps within a SwiftUI interface, complete with dynamic marker rendering and camera adjustments.
import ComposeApp
import GoogleMaps
import SwiftUI
struct MapView: UIViewRepresentable {
var coordinates: [Coordinate]
func makeUIView(context: Context) -> GMSMapView {
let options = GMSMapViewOptions()
let path = GMSMutablePath()
let gmsMapView = GMSMapView(options: options)
for coordinate in coordinates {
let marker = GMSMarker()
let location = CLLocationCoordinate2D(
latitude: coordinate.latitude, longitude: coordinate.longitude)
marker.position = location
marker.title = "iOS Native Marker"
marker.map = gmsMapView
path.add(location)
}
let bounds = GMSCoordinateBounds(path: path)
gmsMapView.animate(with: GMSCameraUpdate.fit(bounds, withPadding: 50.0))
return gmsMapView
}
func updateUIView(_ uiView: GMSMapView, context: Context) {}
}
Open the ContentView.swift file and define the mapUIViewController argument as in the following code:
import ComposeApp
import SwiftUI
import UIKit
struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController(
mapUIViewController: {
(coordinates: [Coordinate]) -> UIViewController in
return UIHostingController(
rootView: MapView(coordinates: coordinates))
}
)
}
...
}
struct ContentView: View {
...
}
Our integration on both sides is complete! Now let’s get to the fun part.
4. Using the MapView
In our commonMain module we now have a composable function called MapView. Let’s make the following changes in the App.kt file for testing purposes:
@Composable
@Preview
fun App() {
MaterialTheme {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
MapView(
modifier = Modifier.fillMaxSize(),
coordinates = listOf(
Coordinate(latitude = 41.015137, longitude = 28.979530),
Coordinate(latitude = 39.925533, longitude = 32.866287),
Coordinate(latitude = 38.423733, longitude = 27.142826),
Coordinate(latitude = 37.575275, longitude = 36.922821),
)
)
}
}
}
Let’s run it and see the result:
Conclusion
Compose Multiplatform makes it easier to write cross-platform applications, and Google Maps adds a powerful tool for location-based features. By following the steps outlined above, you can successfully integrate Google Maps into your project.
Let me know if you’d like to dive deeper into any section or need further edits!
The complete source code for this project is available on GitHub: https://github.com/serkancay/googlemaps-integration-cmp
Featured Photo by Aleksejs Bergmanis from Pexels: https://www.pexels.com/photo/aerial-photo-of-buildings-and-roads-681335/
Leave a Reply