Google Maps is a contemporary toolkit that gives detailed details about geographical areas. At the moment, it has greater than a billion customers per day.
Nonetheless, it will get difficult while you need to use the previous library, Maps SDK for Android, with Jetpack Compose. You could write advanced and sometimes giant View interoperability code blocks to mix Jetpack Compose with the usual map UI part – MapView. This opposes one in all Jetpack Compose’s main aims of being easy and exact. To resolve this, Google created a brand new and easier approach of dealing with Google Maps in Jetpack Compose initiatives.
In February 2022, Google launched the Maps Compose library. It’s an open-source set of composable capabilities that simplify Google Maps implementation. Moreover that, the library comprises particular knowledge sorts associated to Maps SDK for Android suited to Jetpack Compose.
On this tutorial, you’ll construct the GeoMarker app. The app permits you to use Maps Compose options like markers, circles and data home windows. Moreover, you’ll additionally have the ability to mark factors in your UI and have the ability to draw a polygon from chosen factors.
In the course of the course of, you’ll be taught:
- Establishing Google Maps in compose.
- Requesting location permissions.
- Including markers, data home windows and circles in your map.
- Including customized map styling.
- Drawing polygons in your map.
- Testing some map options.
Getting Began
Obtain the starter venture by clicking Obtain Supplies on the high or backside of the tutorial.
Open Android Studio Chipmunk or later and import the starter venture. Construct and run the venture. You’ll see the next screens:
The app reveals an empty display screen with a ‘Mark Space’ floating motion button on the backside. You’ll show your map and different map parts on this display screen. You’ll additionally add the geo-marking performance.
Setting Up
To begin engaged on maps in compose, you could full the next steps:
- Establishing the dependencies:
- Secondly, you want a Google Maps API key for you to have the ability to use any of Google Maps APIs. You’ll find directions on easy methods to get your key right here. After getting your key, proceed so as to add it to your native.properties file as follows:
MAPS_API_KEY=YOUR_API_KEY
implementation 'com.google.maps.android:maps-compose:2.4.0'
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.gms:play-services-location:20.0.0'
The primary is the Maps Compose library, and the opposite two are the Play Providers maps SDK and site SDKs. Notice that these dependencies exist already within the starter venture, so there’s no must re-add them.
Now that you’ve got every little thing set, time to get your fingers soiled with maps in compose. You’ll begin by requesting location permissions in your app.
Requesting Location Permissions
Your app wants location permissions for you to have the ability to present maps. Head over to presentation/screens/MapScreenContent.kt. Exchange //TODO Add Permissions
with:
// 1
val scope = rememberCoroutineScope()
// 2
val context = LocalContext.present
// 3
var showMap by rememberSaveable {
mutableStateOf(false)
}
// 4
PermissionDialog(
context = context,
permission = Manifest.permission.ACCESS_FINE_LOCATION,
permissionRationale = stringResource(id = R.string.permission_location_rationale),
snackbarHostState = snackbarHostState) { permissionAction ->
// 5
when (permissionAction) {
is PermissionAction.PermissionDenied -> {
showMap = false
}
is PermissionAction.PermissionGranted -> {
showMap = true
scope.launch {
snackbarHostState.showSnackbar("Location permission granted!")
}
fetchLocationUpdates.invoke()
}
}
}
To resolve errors, substitute your imports on the high with:
import android.Manifest
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.composegeomarker.R
import com.android.composegeomarker.permissions.PermissionAction
import com.android.composegeomarker.permissions.PermissionDialog
import kotlinx.coroutines.launch
Right here’s what the code above does:
- You create a
CoroutineScope
variable you’ll use to indicate your Snackbar. - This can be a variable to get the context of your present composable.
- You’ve got a Boolean variable
showMap
that represents whether or not the app has vital permissions. - Right here, you name
PermissionDialog
, a customized composable that handles all of the permissions logic. - The
PermissionDialog
has a callback that returns which permission choice the consumer has chosen. It might probably both bePermissionGranted
orPermissionDenied
. On every of this, you replace theshowMap
variable. When the consumer grants the permission, you present a Snackbar with a “Location permission granted!” message and begin the placement updates.
With this, you’re prepared to indicate places on a map, and that’s the following step.
Displaying a Place in a Map
Navigate to presentation/composables/MapView.kt. You’ll see two TODOs that you just’ll work on in a second.
However earlier than that, substitute your imports with the next:
import android.content material.Context
import androidx.compose.basis.structure.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.google.android.gms.maps.mannequin.CameraPosition
import com.google.android.gms.maps.mannequin.LatLng
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.rememberCameraPositionState
Begin by changing // TODO add Digital camera Place State
with:
val cameraPositionState = rememberCameraPositionState {
place = CameraPosition.fromLatLngZoom(location, 16f)
}
Within the code above, you create a CameraPositionState
occasion, which holds the configurations in your map. On this case, you set your map’s location and zoom stage.
Second, substitute // TODO Add Google Map
with:
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
)
GoogleMap
is a container for a MapView
, to which you move each values, modifier
and cameraPositionState
. And that’s all you might want to present a single location on a map in compose :]
Final, you might want to name your customized MapView
composable in your MapScreenContent.kt. You move within the context and site as parameters. For an instance, you’ll use a set location in Singapore. Return to presentation/screens/MapScreenContent.kt and beneath PermissionDialog
add:
val currentLocation = LatLng(1.35, 103.87)
if (showMap) {
MapView(context, currentLocation)
}
Add the next imports to your import statements to resolve the errors.
import com.android.composegeomarker.presentation.composables.MapView
import com.google.android.gms.maps.mannequin.LatLng
Right here, you added the conditional to verify whether or not your map needs to be displayed. As soon as the situation is met, you name MapView
passing within the context and present location.
Construct and run the app:
The app now reveals the placement in Singapore on the map. Within the subsequent part, you’ll add a marker to this location.
Including a Marker on the Map
Inside presentation/composables/MapView.kt, add a pair of curly braces to GoogleMap
composable and add the next within the block:
Marker(
state = MarkerState(place = location),
)
Add any lacking imports by urgent Possibility-Return on a Mac or Alt-Enter on a Home windows PC. Your closing end result shall be:
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
) {
Marker(
state = MarkerState(place = location),
)
}
You add a marker in a map by including baby composables to GoogleMap
as contents. A Marker
requires a MarkerState
occasion that observes marker state akin to its place and knowledge window.
Move the Singapore location to MarkerState
after which construct and run the app.
You’ll be able to see the crimson marker for Singapore on the middle of your map.
Usually, you’ll want to indicate info when a consumer faucets a marker. For that, you’ll have so as to add InfoWindow
to your map, which you’ll be taught subsequent.
Exhibiting Map Data Home windows
Head again to presentation/composables/MapView.kt and add this code beneath the cameraPositionState
variable:
val infoWindowState = rememberMarkerState(place = location)
You’ve got now created a state variable for the marker properties and handed the placement to this marker.
Subsequent, beneath your Marker
composable, add:
MarkerInfoWindow(
state = infoWindowState,
title = "My location",
snippet = "Location customized data window",
content material = {
CustomInfoWindow(title = it.title, description = it.snippet)
}
)
Within the code above, you create your info window utilizing MarkerInfoWindow
composable. You’ll be able to customise your info window to your liking. You move the state
, title
, snippet
and content material
as parameters. Contained in the content material lambda, you name your customized composable together with your info window customized view.
Construct and run the app. Faucet the Singapore marker, and you must see:
The data window shows on high of the marker with texts from the title
and snippet
you handed as parameters.
Drawing Circles on Your Map
To date, you’ve seen easy methods to add markers and data home windows to your map. On this part, you’ll add one other form, a Circle
.
In MapView.kt, add the next beneath MarkerInfoWindow
within the GoogleMap
composable:
Circle(
middle = location,
fillColor = MaterialTheme.colorScheme.secondaryContainer,
strokeColor = MaterialTheme.colorScheme.secondaryContainer,
radius = 300.00
)
Resolve the MaterialTheme
lacking imports by urgent Possibility-Return on a Mac or Alt-Enter on a PC.
Circle
is yet one more map baby composable and has a number of parameters. For this tutorial, you solely must assign values to:
- middle – the
LatLng
that represents the middle of this circle. - fillColor – fill coloration of the circle.
- strokeColor – coloration of the outer circle or stroke.
- radius – circle radius.
Construct and run the app.
Now you can see a blue circle on the middle of your map. Its middle is the Singapore location that you just handed.
To date, you’ve drawn a number of shapes in your map. Within the subsequent part, you’ll learn to customise your map’s look by including a customized JSON map type.
Customizing the Look of Your Map
There are two map styling choices accessible with maps:
- Cloud-based styling: This lets you create and edit map kinds with out requiring any adjustments in your app. You make all of the adjustments within the cloud console, that are mirrored in your apps after you have a map ID.
- JSON primarily based styling: Right here, you create a map type on the outdated type wizard . When you full the customization, you may obtain the JSON file and add it to your map.
On this tutorial, you’ll be utilizing JSON styling. You’ll create your customized type so as to add to the map within the subsequent part.
Making a Customized JSON Map Styling
Open your most well-liked browser and head to the outdated type wizard. It’s best to see:
On the left, you’ve got customization choices akin to altering the density of the options and altering the theme of your map.
Begin by choosing the Silver theme as proven beneath:
On the correct aspect, you may see the map coloration adjustments to mirror the chosen theme. Subsequent, click on MORE OPTIONS as proven above.
This reveals a listing of options you may customise and visualize on the map. For this tutorial, you’ll customise the Highway characteristic.
Observe these steps:
- Click on the Highway characteristic, which can open up the aspect kind part on the correct.
- The weather kind part has a listing of parts you may customise, which on this case are labels and geometry.
- Click on the Geometry choice and alter the colour as per your choice. You’ll be able to see the colour is instantly mirrored on the map.
That’s all for now. You’ll be able to add as many customization choices as you want. Click on FINISH, and also you’ll see the Export Fashion dialog as proven:
Click on COPY JSON choice. This copies the JSON type in your clipboard. You’re now a couple of steps away from making use of the customized type to your compose map.
Navigate again to Android Studio. Proper-click the res listing, select New ▸ Android Useful resource Listing and choose uncooked. Within the new uncooked listing, create a file named map_style.json and paste the copied type right here.
Now, you’ve got the type prepared to be used. Subsequent, you might want to apply it to your map.
Making use of Customized Fashion to Your Map
Head over to presentation/composables/MapView.kt. Under your infoWindowState
variable add:
val mapProperties by bear in mind {
mutableStateOf(
MapProperties(
mapStyleOptions = MapStyleOptions.loadRawResourceStyle(context, R.uncooked.map_style)
)
)
}
Add any lacking imports by urgent Possibility-Return on a Mac or Alt-Enter on a PC. As seen above, you create a brand new state variable of kind MapProperties
. This variable holds properties you may change on the map. You move the customized type to the mapStyleOptions
, which hundreds the type from the uncooked listing.
Subsequent, add this variable mapProperties
as properties
parameter to your GoogleMap
. Your closing end result needs to be:
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState,
properties = mapProperties
) {
// Little one Composables
}
Construct and run the app.
You’ll be able to see your map now applies the type out of your JSON file.
Requesting Location Updates
Notice: This part is elective. You’ll be able to skip forward to Marking Polygon Positions if you wish to begin including your geo marking performance. Nonetheless, should you’d like to know easy methods to do location updates, you’re in the correct place! The performance is already within the starter venture.
A typical characteristic of maps on units is the flexibility for them to replace in actual time. To do this right here, You’ll use a callbackFlow
to request for location updates. Inside utils package deal you’ll discover LocationUtils.kt file. The situation callbackFlow
is as follows:
@SuppressLint("MissingPermission")
enjoyable FusedLocationProviderClient.locationFlow() = callbackFlow {
val callback = object : LocationCallback() {
override enjoyable onLocationResult(end result: LocationResult) {
attempt {
trySend(end result.lastLocation)
} catch (e: Exception) {
Log.e("Error", e.message.toString())
}
}
}
requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
.addOnFailureListener { e ->
shut(e)
}
awaitClose {
removeLocationUpdates(callback)
}
}
Right here, you wrap your LocationCallback
in a callbackFlow
. Within the callbackFlow
, callback
is known as each time you’ve got location updates from requestLocationUpdates
. And eventually, you clear up sources when your callback is eliminated inside awaitClose
.
Open up MainActivity.kt, and take a look at fetchLocationUpdates()
to see the way it fetches location updates:
non-public enjoyable fetchLocationUpdates() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
fusedLocationClient.locationFlow().acquire {
it?.let { location ->
geoMarkerViewModel.setCurrentLatLng(LatLng(location.latitude, location.longitude))
}
}
}
}
}
This makes use of repeatOnLifecycle()
to gather safely out of your Movement within the UI. You additionally move the placement to your viewmodel to share the most recent worth together with your composable.
Within the subsequent part, you’ll see how to attract polygons in your map and end the geo marking a part of the app.
Marking Polygon Positions
There are two choices accessible to create your geo marker:
- Drawing polylines: You employ the placement replace characteristic to attract polylines as a consumer walks in a sure space. You draw polylines after a consumer updates their location at set intervals.
- Draw polygons: You draw polygons from a listing of
LatLng
coordinates. For this tutorial, you’ll be utilizing this feature.
Head over to presentation/screens/GeoMarkerScreen.kt and also you’ll see:
On this file, you’ve got a GeoMarkerScreen
composable that has a number of map state variables outlined. It has a Scaffold
inside the place you’ve got your GoogleMap
composable. You’ve got three TODOs you’ll tackle in a second.
Construct and run the app. Faucet Mark Space.
You’ll be able to see the map and a button on the backside of the map. You’ll be including performance for including geo factors by clicking any three factors on the map.
To start with, substitute // TODO Add click on listener
with:
if (!drawPolygon) {
showSavePoint = true
clickedLocation = it
}
Right here, you do a conditional verify to verify whether or not the polygon is already drawn. When the situation isn’t happy, you replace the showSavePoint
, which is a Boolean that determines whether or not to indicate the UI to save lots of the clicked level. Clicking a map additionally returns a LatLng
of the clicked level. You assign this worth to the clickedLocation
variable.
Subsequent, substitute // TODO Save Level UI
with:
if (showSavePoint) {
SaveGeoPoint(latLng = clickedLocation) {
showSavePoint = it.hideSavePointUi
areaPoints.add(it.level)
}
} else {
if (areaPoints.isEmpty()) {
Textual content(
modifier = Modifier
.fillMaxWidth(),
coloration = Shade.Blue,
textual content = "Click on any level on the map to mark it.",
textAlign = TextAlign.Heart,
fontWeight = FontWeight.Daring
)
}
}
Add any lacking imports by urgent Possibility-Return on a Mac or Alt-Enter on a PC. You add one other conditional verify.
When showSavePoint
is true, you present the SaveGeoPoint
composable. SaveGeoPoint
is a customized composable with UI for saving the clicked level. You move the clickedLocation
from the map click on listener. When the situation evaluates to false, you present a textual content with directions on easy methods to mark factors on the map.
Construct and run the app. Navigate to the Geo Marker Display screen as soon as extra. You’ll see:
Faucet any level on the map.
You’ll be able to see the UI to save lots of the purpose in your map. It shows the LatLng
and the Save Level motion which saves your level.
You’ll discover while you save three factors that the Full button on the backside turns into lively. Faucet Full. Nothing occurs on the map; it solely reveals a reset button. Like me, you had been anticipating to see a polygon. Don’t fear. You’ll repair this habits in a second.
Exchange // TODO Add Polygon
with:
// 1
if (drawPolygon && areaPoints.isNotEmpty()) {
// 2
areaPoints.forEach {
Marker(state = MarkerState(place = it))
}
// 3
Polygon(
factors = areaPoints,
fillColor = Shade.Blue,
strokeColor = Shade.Blue
)
}
// 4
if (showSavePoint) {
Marker(state = MarkerState(place = clickedLocation))
}
Add any lacking imports by urgent Possibility-Return on a Mac or Alt-Enter on a PC.
Right here’s what the code above does:
- This can be a conditional verify to verify whether or not the polygon is drawn. You additionally verify if the
areaPoints
has values since you want a listing ofLatLng
to attract a polygon. - Right here, for every merchandise within the
areaPoints
checklist, you add a marker in your map. - You employ
Polygon
composable, to attract your polygon. You move within the factors to attract and the colours in your polygon. - This can be a marker for every level you click on on the map.
Construct and run the app, then faucet the marker space button and add three markers. Lastly, faucet the whole button.
Congratulations! You’ve been capable of create a geo marker with a polygon. You’ll be able to reset the map and draw as many polygons as you need.
Writing Map UI Exams
Exams are often essential in any piece of software program. Google Map Compose library was not left behind when it comes to writing assessments in your map logic. To make it extra fascinating, it’s simpler so that you can write the UI assessments in your map composables.
Head over to your androidTest listing and open GoogleMapTest.kt. The take a look at class GoogleMapTest
solely has a helpful setup
methodology that runs earlier than your assessments run. It initializes a CameraPositionState
with a location and a zoom stage.
Earlier than writing your assessments, you might want to arrange your map. Add the next methodology beneath the setup
methodology:
non-public enjoyable loadMap() {
val countDownLatch = CountDownLatch(1)
composeTestRule.setContent {
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState,
onMapLoaded = {
countDownLatch.countDown()
}
)
}
val mapLoaded = countDownLatch.await(30, TimeUnit.SECONDS)
assertTrue("Map loaded", mapLoaded)
}
Exchange your imports on the high with:
import androidx.compose.basis.structure.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.take a look at.junit4.createComposeRule
import com.google.android.gms.maps.mannequin.CameraPosition
import com.google.android.gms.maps.mannequin.LatLng
import com.google.maps.android.compose.CameraPositionState
import com.google.maps.android.compose.GoogleMap
import junit.framework.Assert.assertTrue
import org.junit.Earlier than
import org.junit.Rule
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
You’ve got a CountDownLatch
to permit ready for the map to load earlier than doing any operation on the map. You set the content material of your display screen with the composeTestRule
. Within the setContent
lambda, you add the GoogleMap
composable. You additionally move the cameraPositionState
modifier
, and inside your onMapLoaded
, you begin your countdown.
Lastly, you carry out an assertion after ready 30 seconds to verify whether or not the map was loaded. You’ll use this methodology to initialize your map in consecutive assessments.
You’ll now add assessments to indicate the digicam place and map zoom stage are set to the right values.
Add the next assessments:
@Take a look at
enjoyable testCameraPosition() {
loadMap()
assertEquals(singapore, cameraPositionState.place.goal)
}
@Take a look at
enjoyable testZoomLevel() {
loadMap()
assertEquals(cameraZoom, cameraPositionState.place.zoom)
}
Within the code above, you’ve got two assessments: one for testing the digicam place and the opposite for testing the zoom stage of your map. In every of those assessments, you name loadMap()
after which assert that the place and zoom stage on the map is much like your preliminary location. Run the take a look at.
You’ll be able to see all of your assessments run efficiently!
The place to Go From Right here?
Obtain the ultimate venture by clicking Obtain Supplies on the high or backside of the tutorial.
You’ll be able to discover the drawing polyline choice to exhibit somebody strolling by way of a subject. You’ll be able to maybe add extra assessments to check your map-related functionalities.
Take a look at the official Google Maps Compose documentation to be taught extra about maps in Compose. To be taught extra about testing your compose layouts, checkout the official testing documentation.
Hopefully, you loved this tutorial. In case you have any questions or feedback, please be a part of the discussion board dialogue beneath!