Giter Club home page Giter Club logo

android-maps-compose's Introduction

Tests Stable Discord Apache-2.0

Maps Compose 🗺

Description

This repository contains Jetpack Compose components for the Maps SDK for Android.

Requirements

  • Kotlin-enabled project
  • Jetpack Compose-enabled project (see releases for the required version of Jetpack Compose)
  • An API key
  • API level 21+

Installation

You no longer need to specify the Maps SDK for Android or its Utility Library as separate dependencies, since maps-compose and maps-compose-utils pull in the appropriate versions of these respectively.

dependencies {
    implementation 'com.google.maps.android:maps-compose:4.4.1'

    // Optionally, you can include the Compose utils library for Clustering,
    // Street View metadata checks, etc.
    implementation 'com.google.maps.android:maps-compose-utils:4.4.1'

    // Optionally, you can include the widgets library for ScaleBar, etc.
    implementation 'com.google.maps.android:maps-compose-widgets:4.4.1'
}

Sample App

This repository includes a sample app.

To run it:

  1. Get a Maps API key
  2. Create a file in the root directory named local.properties with a single line that looks like this, replacing YOUR_KEY with the key from step 1: MAPS_API_KEY=YOUR_KEY
  3. Build and run

Documentation

You can learn more about all the extensions provided by this library by reading the reference documents.

Usage

Adding a map to your app looks like the following:

val singapore = LatLng(1.35, 103.87)
val cameraPositionState = rememberCameraPositionState {
    position = CameraPosition.fromLatLngZoom(singapore, 10f)
}
GoogleMap(
    modifier = Modifier.fillMaxSize(),
    cameraPositionState = cameraPositionState
)
Creating and configuring a map

Creating and configuring a map

Configuring the map can be done by passing a MapProperties object into the GoogleMap composable, or for UI-related configurations, use MapUiSettings. MapProperties and MapUiSettings should be your first go-to for configuring the map. For any other configuration not present in those two classes, use googleMapOptionsFactory to provide a GoogleMapOptions instance instead. Typically, anything that can only be provided once (i.e. when the map is created)—like map ID—should be provided via googleMapOptionsFactory.

// Set properties using MapProperties which you can use to recompose the map
var mapProperties by remember {
    mutableStateOf(
        MapProperties(maxZoomPreference = 10f, minZoomPreference = 5f)
    )
}
var mapUiSettings by remember {
    mutableStateOf(
        MapUiSettings(mapToolbarEnabled = false)
    )
}
Box(Modifier.fillMaxSize()) {
    GoogleMap(properties = mapProperties, uiSettings = mapUiSettings)
    Column {
        Button(onClick = {
            mapProperties = mapProperties.copy(
                isBuildingEnabled = !mapProperties.isBuildingEnabled
            )
        }) {
            Text(text = "Toggle isBuildingEnabled")
        }
        Button(onClick = {
            mapUiSettings = mapUiSettings.copy(
                mapToolbarEnabled = !mapUiSettings.mapToolbarEnabled
            )
        }) {
            Text(text = "Toggle mapToolbarEnabled")
        }
    }
}

// ...or initialize the map by providing a googleMapOptionsFactory
// This should only be used for values that do not recompose the map such as
// map ID.
GoogleMap(
    googleMapOptionsFactory = {
        GoogleMapOptions().mapId("MyMapId")
    }
)
Controlling a map's camera

Controlling a map's camera

Camera changes and updates can be observed and controlled via CameraPositionState.

Note: CameraPositionState is the source of truth for anything camera related. So, providing a camera position in GoogleMapOptions will be overridden by CameraPosition.

val singapore = LatLng(1.35, 103.87)
val cameraPositionState: CameraPositionState = rememberCameraPositionState {
    position = CameraPosition.fromLatLngZoom(singapore, 11f)
}
Box(Modifier.fillMaxSize()) {
  GoogleMap(cameraPositionState = cameraPositionState)
  Button(onClick = {
    // Move the camera to a new zoom level
    cameraPositionState.move(CameraUpdateFactory.zoomIn())
  }) {
      Text(text = "Zoom In")
  }
}
Drawing on a map

Drawing on a map

Drawing on the map, such as adding markers, can be accomplished by adding child composable elements to the content of the GoogleMap.

GoogleMap(
    googleMapOptionsFactory = {
        GoogleMapOptions().mapId("DEMO_MAP_ID")
    },
    //...
) {
    AdvancedMarker(
        state = MarkerState(position = LatLng(-34, 151)),
        title = "Marker in Sydney"
    )
    AdvancedMarker(
        state = MarkerState(position = LatLng(35.66, 139.6)),
        title = "Marker in Tokyo"
    )
}

You can customize a marker by using PinConfig with an AdvancedMarker.

val state = MyState()

GoogleMap(
    googleMapOptionsFactory = {
        GoogleMapOptions().mapId("DEMO_MAP_ID")
    },
    //...
) {
    val pinConfig = PinConfig.builder()
        .setBackgroundColor(Color.MAGENTA)
        .build()

    AdvancedMarker(
        state = MarkerState(position = LatLng(-34, 151)),
        title = "Magenta marker in Sydney",
        pinConfig = pinConfig
    )
}
Shapes

Shapes

A shape is an object on the map, tied to a latitude/longitude coordinate. Currently, android-maps-compose offers Polyline, Polygon and Circle. For all shapes, you can customize their appearance by altering a number of properties.

Polyline

A Polyline is a series of connected line segments that can form any shape you want and can be used to mark paths and routes on the map:

val polylinePoints = remember { listOf(singapore, singapore5) }

// ... 
Polyline(
    points = polylinePoints
)

You can use spans to individually color segments of a polyline, by creating StyleSpan objects:

val styleSpan = StyleSpan(
    StrokeStyle.gradientBuilder(
        Color.Red.toArgb(),
        Color.Green.toArgb(),
    ).build(),
)

// ...

val polylinePoints = remember { listOf(singapore, singapore5) }
val styleSpanList = remember { listOf(styleSpan) }

// ... 

Polyline(
    points = polylinePoints,
    spans = styleSpanList,
)

Polygon

A Polygon is an enclosed shape that can be used to mark areas on the map:

val polygonPoints = remember { listOf(singapore1, singapore2, singapore3) }


// ... 

Polygon(
    points = polygonPoints,
    fillColor = Color.Black.copy(alpha = 0.5f)
)

Circle

A Circle is a geographically accurate projection of a circle on the Earth's surface drawn on the map:

var circleCenter by remember { mutableStateOf(singapore) }

// ... 

Circle(
    center = circleCenter,
    fillColor = MaterialTheme.colors.secondary,
    strokeColor = MaterialTheme.colors.secondaryVariant,
    radius = 1000.0,
)
Recomposing elements

Recomposing elements

Markers and other elements need to be recomposed in the screen. To achieve recomposition, you can set mutable properties of state objects:

val markerState = rememberMarkerState(position = singapore)

//...

LaunchedEffect(Unit) {
    repeat(10) {
        delay(5.seconds)
        val old = markerState.position
        markerState.position = LatLng(old.latitude + 1.0, old.longitude + 2.0)
    }
}

In the example above, recomposition occurs as MarkerState.position is updated with different values over time, shifting the Marker around the screen.

Customizing a marker's info window

Customizing a marker's info window

You can customize a marker's info window contents by using the MarkerInfoWindowContent element, or if you want to customize the entire info window, use the MarkerInfoWindow element instead. Both of these elements accept a content parameter to provide your customization in a composable lambda expression.

MarkerInfoWindowContent(
    //...
) { marker ->
    Text(marker.title ?: "Default Marker Title", color = Color.Red)
}

MarkerInfoWindow(
    //...
) { marker ->
    // Implement the custom info window here
    Column {
        Text(marker.title ?: "Default Marker Title", color = Color.Red)
        Text(marker.snippet ?: "Default Marker Snippet", color = Color.Red)
    }
}
Street View

Street View

You can add a Street View given a location using the StreetView composable.

  1. Test whether a Street View location is valid with the the fetchStreetViewData utility from the maps-compose-utils library.
 streetViewResult =
    fetchStreetViewData(singapore, BuildConfig.MAPS_API_KEY)
  1. Once the location is confirmed valid, add a Street View composable by providing a StreetViewPanoramaOptions object.
val singapore = LatLng(1.3588227, 103.8742114)
StreetView(
    streetViewPanoramaOptionsFactory = {
        StreetViewPanoramaOptions().position(singapore)
    }
)
Controlling the map directly (experimental)

Controlling the map directly (experimental)

Certain use cases may require extending the GoogleMap object to decorate / augment the map. It can be obtained with the MapEffect Composable. Doing so can be dangerous, as the GoogleMap object is managed by this library.

GoogleMap(
    // ...
) {
    MapEffect { map ->
        // map is the GoogleMap
    }
}

Maps Compose Utility Library

This library provides optional utilities in the maps-compose-utils library from the Maps SDK for Android Utility Library.

Clustering

The marker clustering utility helps you manage multiple markers at different zoom levels. When a user views the map at a high zoom level, the individual markers show on the map. When the user zooms out, the markers gather together into clusters, to make viewing the map easier.

The MarkerClusteringActivity demonstrates usage.

Clustering(
    items = items,
    // Optional: Handle clicks on clusters, cluster items, and cluster item info windows
    onClusterClick = null,
    onClusterItemClick = null,
    onClusterItemInfoWindowClick = null,
    // Optional: Custom rendering for clusters
    clusterContent = null,
    // Optional: Custom rendering for non-clustered items
    clusterItemContent = null,
)

Street View metadata utility

The fetchStreetViewData method provides functionality to check whether a location is supported in StreetView. You can avoid errors when adding a Street View panorama to an Android app by calling this metadata utility and only adding a Street View panorama if the response is OK.

Important

Be sure to enable Street View Static API on the project associated with your API key.

You can see example usage in the StreetViewActivity of the demo app:

 streetViewResult =
    fetchStreetViewData(singapore, BuildConfig.MAPS_API_KEY)

Maps Compose Widgets

This library also provides optional composable widgets in the maps-compose-widgets library that you can use alongside the GoogleMap composable.

ScaleBar

This widget shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. A DisappearingScaleBar is also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period.

The ScaleBarActivity demonstrates both of these, with the DisappearingScaleBar in the upper left corner and the normal base ScaleBar in the upper right:

maps-compose-scale-bar-cropped

Both versions of this widget leverage the CameraPositionState in maps-compose and therefore are very simple to configure with their defaults:

Box(Modifier.fillMaxSize()) {

    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState
    ) {
        // ... your map composables ...
    }

    ScaleBar(
        modifier = Modifier
            .padding(top = 5.dp, end = 15.dp)
            .align(Alignment.TopEnd),
        cameraPositionState = cameraPositionState
    )

    // OR

    DisappearingScaleBar(
        modifier = Modifier
            .padding(top = 5.dp, end = 15.dp)
            .align(Alignment.TopStart),
        cameraPositionState = cameraPositionState
    )
}

The colors of the text, line, and shadow are also all configurable (e.g., based on isSystemInDarkTheme() on a dark map). Similarly, the DisappearingScaleBar animations can be configured.

Contributing

Contributions are welcome and encouraged! See contributing for more info.

Support

This library is offered via an open source license. It is not governed by the Google Maps Platform Technical Support Services Guidelines, the SLA, or the Deprecation Policy (however, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service).

This library adheres to semantic versioning to indicate when backwards-incompatible changes are introduced.

If you find a bug, or have a feature request, please file an issue on GitHub.

If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our developer community channels including the Google Maps Platform Discord server.

android-maps-compose's People

Contributors

adamp avatar arriolac avatar barbeau avatar bubenheimer avatar chibatching avatar claytongreen avatar darrylbayliss avatar dependabot[bot] avatar dkhawk avatar dsteve595 avatar el-qq avatar googlemaps-bot avatar jpoehnelt avatar kikoso avatar lawm avatar matsudamper avatar mosmb avatar oas004 avatar polivmi1 avatar priyankkjain avatar romainpiel avatar semantic-release-bot avatar wangela avatar webfrea-k avatar y9san9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

android-maps-compose's Issues

Requirements should mention Jetpack Compose 1.2.x

This is more of a documentation oversight.
The "Requirements" and/or the "Installation" sections in README.md should mention that this SDK is only compatible with Jetpack Compose 1.2.x.
I lost about an hour trying to make this work in my Compose 1.1.x-using project.

Incorrect property name `mapProperties` on README

This is only a documentation error.

GoogleMap composable doesn't have mapProperties just having properties.

GoogleMap(mapProperties = mapProperties)

Environment details

  1. Specify the API at the beginning of the title (for example, "Places: ...")
    • GoogleMap
  2. OS type and version
    • not specified
  3. Library version and other environment information
    • 1.0.0

Camera stopped during a cancellation

Hello,

I have a map in a Box where it has some BottomSheet like composables on top that appear from the bottom when you click on some of the markers, kind of google maps style.
The problem comes when I flick the map and before the camera stops moving I click anywhere in the map to stop moving it, then I get this exception :

com.google.maps.api.android.lib6.common.apiexception.c: Camera stopped during a cancellation

I thought it was related to having composables on top, but the problem is still there if I remove them.
The problem is only occuring with fast camera movements and suddenly stopping them clicking the map or some marker.

Any ideas what could it be?
Thank you!

Define custom marker images with Composables

For cases where info windows aren't flexible enough, or the marker images need to be dynamic in some way, it'd be great to be able to use a @Composable to render the marker images.

With Views, this was possible by creating a view and using View.drawToBitmap, which should roughly also be possible with a ComposeView (with caveats, due to the use of software rendering).

Bug with Bounds method

I want to find range of coordinates visible on the screen so i can zoom enough to see my both markers with specific coordinates, I tried to use this fun MapProperties().latLngBoundsForCameraTarget, but I always get null, maybe it is a bug?

Add GeoJsonLayer support to Compose map

Is your feature request related to a problem? Please describe.

In order to be able to use GeoJson, we need GeoJsonLayer support.

Describe the solution you'd like
A clear and concise description of what you want to happen.

A new Composable to add GeoJson shape.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Expose GoogleMap instance, so we can add GeoJsonLayer and update the map instance.

Provide a method to know if the map failed to load

Is your feature request related to a problem? Please describe.
Referencing the sample app, if you fail to provide an API key it will show a loading indicator forever. It would be good to provide a callback for if the map failed to load so that an error could be displayed.

Describe the solution you'd like
Another callback could be provided for onMapError {}

Describe alternatives you've considered
Alternatively, onMapLoaded could return if it was successful in loading or not

How to traverse through markers in google maps?

PLEASE READ

If you have a support contract with Google, please create an issue in the support console. This will ensure a timely response.

Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform support resources page.

If your bug or feature request is not related to this particular library, please visit the Google Maps Platform issue trackers.

Check for answers on StackOverflow with the google-maps tag.


MapApplier and MapNode is not public

Is your feature request related to a problem? Please describe.

In order to be able to add more functionality with GoogleMap instance, I wanted to create new Composable like existing ones; Polygon, Polyline... But MapApplier and MapNode are internal and we cannot extend the composable google map functionality.

We already have classes to configure GoogleMap instance. If we can write our own Composable function to update GoogleMap instance, we can migrate to Compose Map much easier.

Describe the solution you'd like
A clear and concise description of what you want to happen.

Make MapApplier and MapNode public

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Create and API to provide GoogleMap instance so we can configure.

Split sample app into several single purpose demos

Is your feature request related to a problem? Please describe.
Currently there is only a single map that attempts to demonstrate how to use all the features of Maps Compose. While useful, it isn't the best way to learn how to use a single feature of the library.

Describe the solution you'd like
Instead, the sample app should be split into several single purpose demos. A list of demos as the main screen, and then a detail view for each feature.

Describe alternatives you've considered
Continue bloating the demo app.

Get visible map bounds

Hello,

Is it possible to have the visible map bounds ? Or can we calculate them with the center coordinates and the zoom ?

Thank you !

help

Thanks for stopping by to let us know something could be better!


PLEASE READ

If you have a support contract with Google, please create an issue in the support console. This will ensure a timely response.

Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform support resources page.

If your bug or feature request is not related to this particular library, please visit the Google Maps Platform issue trackers.

Check for answers on StackOverflow with the google-maps tag.


Please be sure to include as much information as possible:

Environment details

  1. Specify the API at the beginning of the title (for example, "Places: ...")
  2. OS type and version
  3. Library version and other environment information

Steps to reproduce

  1. ?

Code example

# example

Stack trace

# example

Following these steps will guarantee the quickest resolution possible.

Thanks!

Implement custom info windows

Is your feature request related to a problem? Please describe.
Custom info windows cannot be implemented at the moment.

Describe the solution you'd like
setInfoWindowAdapter should be exposed on the GoogleMap or Marker composable. The implementation should allow using Compose for setting the content or info window vs. having to use Views.

googleMapOptionsFactory doesn't zoom to the provided LatLng

There is a bug with the following code that is straight from here
The thing Is, the camera is not zoomed to the LatLng! The map view on the app just shows the default camera position.

I actually had to create a cameraPositionState using rememberCameraPositionState, and pass it to the GoogleMap "cameraPositionState = " parameter, I also got rid of the googleMapOptionsFactory as it doesn't do anything apparently.

Is it possible to get map update event?

Now we have a nice GoogleMap composable function perfectly running here, but we were not able to get notification of user interactions with it.

Is it possible to associate an handler to an update/redraw/zooming/panning event?

Is it possible to get in some way information regarding the actual position of camera, bound of visible window, zoom level and so on?

Possible memory leak when GoogleMap component is disposed but Activity is not destroyed

It seems like there could be a possible memory leak when the GoogleMap is disposed without the activity being destroyed. When it is disposed, the lifecycle observer is removed and onDestroy seems to not be called on the map. Does that seem accurate or is onDestroy being called somewhere else as well?

Steps to reproduce:

  1. Modify the onCreate method in MapSampleActivity to conditionally add the map to the composition
    a. See sample code below
  2. launch the sample application
  3. optionally place a breakpoint in the onDispose block of MapLifecycle in GoogleMap.kt
  4. optionally place a breakpoint at the start of the when inside the lifecycleObserver extension function in GoogleMap.kt
  5. optionally Attach the debugger
  6. press the "Hide map" button at the bottom of the screen
  7. Notice that onDispose is called but onDestroy was not called for the MapView since the activity is still alive
  8. Backing out of the app at this point destroys the activity but does not result in any calls to onDestroy for the MapView as far as I can tell
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            var isMapLoaded by remember { mutableStateOf(false) }

            var shouldShowMap by remember { mutableStateOf(true) }

            Box(Modifier.fillMaxSize()) {
                if (shouldShowMap) {
                    GoogleMapView(
                        modifier = Modifier.matchParentSize(),
                        onMapLoaded = {
                            isMapLoaded = true
                        }
                    )
                }
                if (!isMapLoaded) {
                    AnimatedVisibility(
                        modifier = Modifier
                            .matchParentSize(),
                        visible = !isMapLoaded,
                        enter = EnterTransition.None,
                        exit = fadeOut()
                    ) {
                        CircularProgressIndicator(
                            modifier = Modifier
                                .background(MaterialTheme.colors.background)
                                .wrapContentSize()
                        )
                    }
                }
                Button(
                    onClick = { shouldShowMap = false },
                    modifier = Modifier.align(Alignment.BottomCenter)
                        .padding(bottom = 16.dp)
                ) {
                    Text(text = "Hide map")
                }
            }
        }
    }

Specify animate duration for the map

Is your feature request related to a problem? Please describe.
There is now way to specify the animation duration for the map

Describe the solution you'd like
I would like to be able to specify the animation duration when calling the animate function on the CameraPositionState object. Right now I find it too slow for my app.

Describe alternatives you've considered

Additional context
I used the animateCamera function from the legacy maps repo before using compose and I may need an option to customize the animation duration.

enter code here

PLEASE READ

If you have a support contract with Google, please create an issue in the support console. This will ensure a timely response.

Discover additional support services for the Google Maps Platform, including developer communities, technical guidance, and expert support at the Google Maps Platform support resources page.

If your bug or feature request is not related to this particular library, please visit the Google Maps Platform issue trackers.

Check for answers on StackOverflow with the google-maps tag.


Add support for clustering

Do you plan on adding clustering to the Compose version of Google Maps? I tried making the switch from AndroidView({ MapView() }) to the new Composable function but struggle to implement the ClusterManager given that I cannot directly reference the map as an input parameter when instantiating the ClusterManager.
(... and thank you for giving us this awesome Composable in the first place 🙏)

CameraPositionState.position is not updated when using .move()

Modifying the CameraStatePosition using .move will not reflect the position property until the map camera is moved (either via .animate or using gestures/UI controls).

Steps to reproduce

  1. Launch the sample app
  2. Turn the 'Camera Animations On' switch to off
  3. Zoom in/out using the "-"/"+" button
  4. Notice that the DebugView camera position is not updated

MarkerInfoWindowContent causing NoSuchElementException with Renderer.LATEST

Environment details

com.google.maps.android:maps-compose:1.3.0

Using Renderer.LATEST (https://developers.google.com/maps/documentation/android-sdk/renderer)

Steps to reproduce

I have a map where markers are added and removed based on the current visible bounds. If I tap a marker so that its info window is shown, move the map so that the marker is now off the screen, and then programatically remove the marker, I get the below Exception.

This only happens using Renderer.LATEST, I can't reproduce with Renderer.LEGACY

Stack trace

java.util.NoSuchElementException: Collection contains no element matching the predicate.
        at com.google.maps.android.compose.MapApplierKt.nodeForMarker(MapApplier.kt:161)
        at com.google.maps.android.compose.MapApplierKt.access$nodeForMarker(MapApplier.kt:1)
        at com.google.maps.android.compose.MapApplier.attachClickListeners$lambda-7(MapApplier.kt:104)
        at com.google.maps.android.compose.MapApplier.$r8$lambda$rI95Mh8wQgq9h26aA0XDFZdfIaE(Unknown Source:0)
        at com.google.maps.android.compose.MapApplier$$ExternalSyntheticLambda3.onInfoWindowClose(Unknown Source:2)
        at com.google.android.gms.maps.zze.zzb(com.google.android.gms:play-services-maps@@18.0.0:1)
        at com.google.android.gms.maps.internal.zzae.zza(com.google.android.gms:play-services-maps@@18.0.0:2)
        at com.google.android.gms.internal.maps.zzb.onTransact(com.google.android.gms:play-services-maps@@18.0.0:3)
        at android.os.Binder.transact(Binder.java:1043)
        at arq.bl(:com.google.android.gms.policy_maps_core_dynamite@[email protected]:2)
        at com.google.maps.api.android.lib6.impl.dd.d(:com.google.android.gms.policy_maps_core_dynamite@[email protected]:3)
        at com.google.maps.api.android.lib6.phoenix.w.run(:com.google.android.gms.policy_maps_core_dynamite@[email protected]:0)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
I

mapToolbarEnabled doesn't work with liteMode

mapToolbarEnabled cannot be disabled when using liteMode = true. It keeps showing on the map

val cameraPositionState: CameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(LatLng(latitude, longitude), 16f)
    }
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState,
        googleMapOptionsFactory = {
            GoogleMapOptions().mapId("StaticMap")
                .mapToolbarEnabled(false)
                .liteMode(true)
        },
        properties = MapProperties(
            mapStyleOptions = MapStyleOptions.loadRawResourceStyle(
                LocalContext.current,
                R.raw.mapstyle_default
            )
        )
    ){
       Marker(
            position = cameraPositionState.position.target,
            anchor = Offset(0.5f, 0.875f)
        )

No static method isTraceInProgress()

java.lang.NoSuchMethodError: No static method isTraceInProgress()Z in class Landroidx/compose/runtime/ComposerKt; or its super classes (declaration of 'androidx.compose.runtime.ComposerKt' appears in /data/app/~~ex5wcn2UwF9AygGG4l0n2Q==/com.mafqud.android-qnIyQNLHppsHRjDR73AKNA==/base.apk)
        at com.google.maps.android.compose.GoogleMapKt.GoogleMap(Unknown Source:6)
        at com.mafqud.android.map.MapScreenKt.MapScreen(MapScreen.kt:33)
        at com.mafqud.android.map.ComposableSingletons$MapFragmentKt$lambda-1$1.invoke(MapFragment.kt:29)
        at com.mafqud.android.map.ComposableSingletons$MapFragmentKt$lambda-1$1.invoke(MapFragment.kt:28)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
        at androidx.compose.material3.TextKt.ProvideTextStyle(Text.kt:261)
        at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:76)
        at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:75)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
        at androidx.compose.material3.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:69)
        at com.mafqud.android.ui.theme.ThemeKt.MafQudTheme(Theme.kt:82)
        at com.mafqud.android.map.ComposableSingletons$MapFragmentKt$lambda-2$1.invoke(MapFragment.kt:28)
        at com.mafqud.android.map.ComposableSingletons$MapFragmentKt$lambda-2$1.invoke(MapFragment.kt:27)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)

Suggestion: don't set default map click listeners other than OnMapClickListener

MapView use a layer approach to handle click on map element. If a click is not handled by a high layer, a lower layer will be able to handle it. For exemple, when a click on a POI occurs, the POI layer will invoke OnPoiClickListener if it is set. If it isn't set the OnMapClickListener will be called.
(Note: I'm not sure of the exact implementation details, I just described what I think it is)

GoogleMap() composable always set OnPoiClickListener, OnMyLocationClickListener, so the fallback to OnMapClickListener and the associate onMapClick callback is never called and you are forced to implement onPOIClick to handle the click even if you don't care about the POI. MapView also has a default behavior for the onMapClick if you don't set OnMapClickListener that you may want to preserve

State change makes map to misbehave

I've Google Map view inside Column which has vertical scrollable area. In there I have some click event to show hide other features. When I change the state mapview starts misbehaving like zoom buttons stops working. Also it would be nice if we can get an example of how to use mapview inside scrollable area without loosing touches.

MarkerInfoWindow skips recomposition

Environment details

  1. Maps-Compose v1.2.0

Steps to reproduce

  1. Declare a state that should be updated inside a MakerInfoWindow
  2. Update the state
  3. MakerInfoWindow will not update

Code example

@Composable
fun MapTest() {

    val empireStateBuilding = LatLng(40.7481672, -73.9859298)

    var uiSettings by remember { mutableStateOf(MapUiSettings()) }
    var properties by remember {
        mutableStateOf(
            MapProperties(mapType = MapType.SATELLITE)
        )
    }

    var downloaded by remember { mutableStateOf(false) }

    var cameraPosition = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(empireStateBuilding, 20f)
    }

    Box {
        GoogleMap(
            modifier = Modifier.fillMaxSize(),
            properties = properties,
            uiSettings = uiSettings,
            cameraPositionState = cameraPosition
        ) {

            MarkerInfoWindow(
                position = empireStateBuilding,
                title = "Empire State Building"
            ) {
                Card(modifier = Modifier.size(width = 200.dp, height = 200.dp)) {
                    Column(modifier = Modifier.fillMaxSize()) {
                        Text(text = it.title!!)
                        Text("Downloaded: $downloaded")
                        Box(modifier = Modifier.size(100.dp)
                        ) {
                            Image(
                                rememberImagePainter(data = "https://www.esbnyc.com/sites/default/files/a1r4P000009TJmGQAW.jpg",
                                    onExecute = { previous, current ->
                                        println("[LOG] Callback")
                                        current.state == ImagePainter.State.Empty || previous?.request != current.request
                                    }
                                ) {
                                    listener(object : ImageRequest.Listener {
                                        override fun onCancel(request: ImageRequest) {
                                            println("[LOG] onCancel")
                                        }

                                        override fun onError(
                                            request: ImageRequest,
                                            throwable: Throwable
                                        ) {
                                            println("[LOG] onError: $throwable")
                                        }

                                        override fun onStart(request: ImageRequest) {
                                            println("[LOG] onStart")
                                        }

                                        override fun onSuccess(
                                            request: ImageRequest,
                                            metadata: ImageResult.Metadata
                                        ) {
                                            println("[LOG] onSuccess")
                                            println("[LOG] downloaded: $downloaded")
                                            downloaded = true
                                            println("[LOG] downloaded: $downloaded")
                                        }
                                    })
                                },
                                contentDescription = it.title,
                            )
                        }
                    }
                }
            }
        }

        Text(
            "Downloaded: $downloaded",
            style = MaterialTheme.typography.h4,
            color = Color.Red
        )
    }
}

Sample Project attached

Screen recording of the issue

issue.mp4

GraphicsLayer modifier makes the map dissapear

Using graphics layer modifier on GoogleMap with alpha parameter, makes the map disappear.

For example, a map inside a Pager, which scales the page on swipe to another page. Relevant code:

graphicsLayer {
    alpha = lerp(
                start = 0.5f,
                stop = 1f,
                fraction = 1f - pageOffset.coerceIn(0f, 1f)
     )
}

The map disappears. Without this, it works as intended.

Add unit tests

The test workflow only validates if the library builds or not. Unit tests should be added.

Animated tile/ground overlay

What is the recommended way for overlay animations?
Before I would create 10 Ground/Tile overlay objects, add them to map and while I'd keep a reference to each one, I'd change transparency property in a loop (in each loop iteration I would hide all but one).

I've tried something like this:

GoogleMap(
/*settings*/
) {
    val currentVisibleIndex: Int by vm.visibleIndex.collectAsState()
    tileOverlays.forEachIndexed { index, tile ->
        TileOverlay(..., transparency = index == currentVisibleIndex)
    }
}

It works okay-ish if you don't move around on the map (or zoom in and out), if you change the camera, the entire GoogleMap {} get's recomposed and this causes lagging as all the tile overlays are re-added to the map (tileOverlay.forEachIndexed above is executed again).
What can I do to achieve smooth animation?
Thanks.

Marker drag event callback

I want to get marker drag event callback and manipulate marker-related work.

So, Marker composable have to have dragging callback parameter like onMarkerDrag onMarkerDragStart onMarkerDragEnd similar with onClick and onInfoWindow~

It is an alternative to OnMarkerDragListener of Android view version.

Button customization is missing

Hello, I have an issue with customization of mylocation button and positioning it in specific area, in general there is no option to customize your button with new icon and so on.

Prevent Maps Compose elements from being used outside of MapApplier's scope

At the moment, GoogleMap composable methods are basic composable method that can be called from any composable.
They dont work outside of the GoogleMap composable. It would be better to have GoogleMapScope interface like ColumnScope, RowScope. And all of GoogleMap methods could be methods of GoogleMapScope and it would be easier to find methods and prevent making mistakes.

Displaying info window as soon as a marker is added

Is your feature request related to a problem? Please describe.
Currently it is not possible to display the info window when adding a marker without the user clicking on the marker.
For reference, on the existing Maps API we can utilize marker.showInfoWindow()

Describe the solution you'd like
A Boolean on the Marker Composable to immediately show the info window.

Why isn't that an actual data class?

/**
* Data class for UI-related settings on the map.
*/
class MapUiSettings(
val compassEnabled: Boolean = true,
val indoorLevelPickerEnabled: Boolean = true,
val mapToolbarEnabled: Boolean = true,
val myLocationButtonEnabled: Boolean = true,
val rotationGesturesEnabled: Boolean = true,
val scrollGesturesEnabled: Boolean = true,
val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true,
val tiltGesturesEnabled: Boolean = true,
val zoomControlsEnabled: Boolean = true,
val zoomGesturesEnabled: Boolean = true,
) {
override fun toString(): String = "MapUiSettings(" +
"compassEnabled=$compassEnabled, indoorLevelPickerEnabled=$indoorLevelPickerEnabled, " +
"mapToolbarEnabled=$mapToolbarEnabled, myLocationButtonEnabled=$myLocationButtonEnabled, " +
"rotationGesturesEnabled=$rotationGesturesEnabled, scrollGesturesEnabled=$scrollGesturesEnabled, " +
"scrollGesturesEnabledDuringRotateOrZoom=$scrollGesturesEnabledDuringRotateOrZoom, " +
"tiltGesturesEnabled=$tiltGesturesEnabled, zoomControlsEnabled=$zoomControlsEnabled, " +
"zoomGesturesEnabled=$zoomGesturesEnabled)"
override fun equals(other: Any?): Boolean = other is MapUiSettings &&
compassEnabled == other.compassEnabled &&
indoorLevelPickerEnabled == other.indoorLevelPickerEnabled &&
mapToolbarEnabled == other.mapToolbarEnabled &&
myLocationButtonEnabled == other.myLocationButtonEnabled &&
rotationGesturesEnabled == other.rotationGesturesEnabled &&
scrollGesturesEnabled == other.scrollGesturesEnabled &&
scrollGesturesEnabledDuringRotateOrZoom == other.scrollGesturesEnabledDuringRotateOrZoom &&
tiltGesturesEnabled == other.tiltGesturesEnabled &&
zoomControlsEnabled == other.zoomControlsEnabled &&
zoomGesturesEnabled == other.zoomGesturesEnabled
override fun hashCode(): Int = Objects.hash(
compassEnabled,
indoorLevelPickerEnabled,
mapToolbarEnabled,
myLocationButtonEnabled,
rotationGesturesEnabled,
scrollGesturesEnabled,
scrollGesturesEnabledDuringRotateOrZoom,
tiltGesturesEnabled,
zoomControlsEnabled,
zoomGesturesEnabled
)
fun copy(
compassEnabled: Boolean = this.compassEnabled,
indoorLevelPickerEnabled: Boolean = this.indoorLevelPickerEnabled,
mapToolbarEnabled: Boolean = this.mapToolbarEnabled,
myLocationButtonEnabled: Boolean = this.myLocationButtonEnabled,
rotationGesturesEnabled: Boolean = this.rotationGesturesEnabled,
scrollGesturesEnabled: Boolean = this.scrollGesturesEnabled,
scrollGesturesEnabledDuringRotateOrZoom: Boolean = this.scrollGesturesEnabledDuringRotateOrZoom,
tiltGesturesEnabled: Boolean = this.tiltGesturesEnabled,
zoomControlsEnabled: Boolean = this.zoomControlsEnabled,
zoomGesturesEnabled: Boolean = this.zoomGesturesEnabled
) = MapUiSettings(
compassEnabled = compassEnabled,
indoorLevelPickerEnabled = indoorLevelPickerEnabled,
mapToolbarEnabled = mapToolbarEnabled,
myLocationButtonEnabled = myLocationButtonEnabled,
rotationGesturesEnabled = rotationGesturesEnabled,
scrollGesturesEnabled = scrollGesturesEnabled,
scrollGesturesEnabledDuringRotateOrZoom = scrollGesturesEnabledDuringRotateOrZoom,
tiltGesturesEnabled = tiltGesturesEnabled,
zoomControlsEnabled = zoomControlsEnabled,
zoomGesturesEnabled = zoomGesturesEnabled
)
}

MapId related cloud based style is not working

GoogleMap(googleMapOptionsFactory = { GoogleMapOptions().mapId("b6fb728f53b6d3e8") })
The code above should apply cloud-based style. Currently no matter which style is set (via mapId), the style is not reflected within the app. It would be really great if changing this (runtime) would immediately reflect on the map.

Map Snapshots

Please add support for taking map snapshots:
GoogleMap::snapshot(@nonnull GoogleMap.SnapshotReadyCallback callback)

CameraPositionState.position is not updated when using Lite Mode

Similar issue as #15 except when in Lite Mode. My assumption is that this is because Lite Mode doesn't support setOnCameraIdleListener and the other listeners

2022-02-10 11:43:52.500 23983-23983/com.google.maps.android.compose W/Google Maps Android API: setOnCameraIdleListener is not supported in Lite Mode
2022-02-10 11:43:52.500 23983-23983/com.google.maps.android.compose W/Google Maps Android API: setOnCameraMoveCanceledListener is not supported in Lite Mode
2022-02-10 11:43:52.500 23983-23983/com.google.maps.android.compose W/Google Maps Android API: setOnCameraMoveStartedListener is not supported in Lite Mode
2022-02-10 11:43:52.501 23983-23983/com.google.maps.android.compose W/Google Maps Android API: setOnCameraMoveListener is not supported in Lite Mode

Steps to reproduce:

  1. Modify the sample application's GoogleMapOptions() to enable Lite Mode
  2. Launch the sample application
  3. Turn the 'Camera Animations On' switch to off
    a. Note: it also doesn't update the position when the switch is on
  4. Zoom in/out with the +/- buttons
  5. Notice that the DebugView camera position is not updated

When used in a LazyColumn, GoogleMap cannot be scrolled vertically

Thanks for your work on this one, it's really great to finally have an official Compose wrapper for Google Maps!

In our use case, we have a custom GoogleMaps wrapper using AndroidView that sits inside a LazyColumn. It is able to be scrolled vertically and horizontally without scrolling the LazyColumn by using a transparent view and calling .requestDisallowInterceptTouchEvent(true) on the parent.

We are hoping to switch to this, but we noticed it still suffers from the original issue we had to work around. Is usage in a LazyColumn a supported use case and is there a more Compose-friendly way to get nested scrolling working properly with this wrapper?

Thanks!

Steps to reproduce

  1. Include GoogleMap in a LazyColumn
  2. Vertically drag on the map to attempt to scroll it
  3. Observe the LazyColumn scrolls instead of the map

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.