Skip to content

[google_maps_flutter] Issue165999 - add onActiveLevelChanged callback to GoogleMap #8923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f5ffb38
Added onActiveLevelChanged to google_maps_ios - DC
ChopinDavid Mar 26, 2025
0a72d03
Changed package dependencies to point to git branch - DC
ChopinDavid Mar 26, 2025
2d13ec4
Added missing method from google_maps_flutter_ios class - DC
ChopinDavid Mar 26, 2025
7f210f9
Made depencies point to David's fork - DC/WE
ChopinDavid Mar 26, 2025
9ad2b5e
Updated dependencies again to only point to git on overrides - DC/WE
ChopinDavid Mar 26, 2025
bf0d7ac
Added onActiveFloorChanged callback to Android - DC/WE
ChopinDavid Mar 26, 2025
eedb8be
Added missing onActiveLevelChanged implementation on HostMapMessageHa…
ChopinDavid Mar 27, 2025
3fbd779
Added onActiveLevelChanged listener to Controller._connectStreams in …
ChopinDavid Mar 27, 2025
6ee1708
Added onActiveLevelChanged overrides on iOS and Android subclasses of…
ChopinDavid Mar 27, 2025
c4bd26c
Set GoogleMapController to the MapView's indoorDisplay's delegate - D…
ChopinDavid Mar 27, 2025
5e01498
Made it so that the entire IndoorLevel object is serialized/passed ov…
ChopinDavid Mar 27, 2025
6afdac9
Made it so that a nil indoorLevel is returned when a building is unse…
ChopinDavid Mar 27, 2025
4d6fc38
Made it so that onActiveFloorChanged is invoked with a null floor whe…
ChopinDavid Mar 27, 2025
eca56ff
Ran formatters - DC
ChopinDavid Mar 28, 2025
0c85de3
Added google_maps_flutter_ios test - DC
ChopinDavid Mar 29, 2025
5e2da5e
Added google_maps_flutter_android test - DC
ChopinDavid Mar 30, 2025
ddf290c
Added google_maps_flutter_platform_interface tests - DC
ChopinDavid Mar 30, 2025
4220a13
Added google_maps_flutter tests - DC
ChopinDavid Mar 31, 2025
5af2905
Re-ran formatters using repo tooling - DC
ChopinDavid Mar 31, 2025
98fd0e2
Upped version numbers and added CHANGELOG entires - DC
ChopinDavid Mar 31, 2025
36bf1e7
Added example - DC
ChopinDavid Mar 31, 2025
157c628
Ran make-deps-path-based - DC
ChopinDavid Apr 1, 2025
b648088
Fixed analyzer warnings - DC
ChopinDavid Apr 1, 2025
d917916
Added missing license blocks - DC
ChopinDavid Apr 1, 2025
917eb3b
Loosened google_maps_flutter_platform_interface constraints on google…
ChopinDavid Apr 1, 2025
9140f43
Converted google_maps_flutter_web's google_maps_flutter dependency to…
ChopinDavid Apr 1, 2025
54af461
Added mockFloorController to GoogleMapControllerTest.java - DC
ChopinDavid Apr 1, 2025
9278285
Reverted changes to google_maps_flutter_web - DC
ChopinDavid Apr 4, 2025
bd32f0c
Merge branch 'main' into Issue165999
reidbaker Apr 11, 2025
9801989
Removed 'publish_to: none
ChopinDavid Apr 21, 2025
0dbba40
Updated onIndoorBuildingFocused override in GoogleMapController.java …
ChopinDavid Apr 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.13.0

* Adds `onActiveLevelChanged` callback to `GoogleMap` to listen for indoor building active floor changes.

## 2.12.1

* Fixes typo in README.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

import 'page.dart';

class IndoorLevelPage extends GoogleMapExampleAppPage {
const IndoorLevelPage({Key? key})
: super(const Icon(Icons.elevator), 'Indoor Levels', key: key);

@override
Widget build(BuildContext context) {
return const IndoorLevelWidget();
}
}

class IndoorLevelWidget extends StatefulWidget {
const IndoorLevelWidget({super.key});
@override
State createState() => IndoorLevelWidgetState();
}

class IndoorLevelWidgetState extends State<IndoorLevelWidget> {
IndoorLevel? _selectedLevel;

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(_selectedLevel == null
? 'No floor selected'
: 'Selected floor name: ${_selectedLevel?.name}\nSelected floor shortName: : ${_selectedLevel?.shortName}'),
SizedBox(
height: 300.0,
width: MediaQuery.of(context).size.width,
child: GoogleMap(
onActiveLevelChanged: (IndoorLevel? selectedLevel) {
setState(() {
_selectedLevel = selectedLevel;
});
},
initialCameraPosition: const CameraPosition(
target: LatLng(36.10741826429339, -115.17673087814264),
zoom: 18),
),
),
],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'animate_camera.dart';
import 'clustering.dart';
import 'ground_overlay.dart';
import 'heatmap.dart';
import 'indoor_level.dart';
import 'lite_mode.dart';
import 'map_click.dart';
import 'map_coordinates.dart';
Expand Down Expand Up @@ -49,6 +50,7 @@ final List<GoogleMapExampleAppPage> _allPages = <GoogleMapExampleAppPage>[
const ClusteringPage(),
const MapIdPage(),
const HeatmapPage(),
const IndoorLevelPage(),
];

/// MapsDemo is the Main Application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@ flutter:
uses-material-design: true
assets:
- assets/
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
google_maps_flutter_android: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_android}
google_maps_flutter_ios: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_ios}
google_maps_flutter_platform_interface: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface}
google_maps_flutter_web: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_web}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf
HeatmapGradientColor,
HeatmapId,
HeatmapRadius,
IndoorLevel,
InfoWindow,
JointType,
LatLng,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class GoogleMapController {
.listen((MapTapEvent e) => _googleMapState.onTap(e.position));
GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen(
(MapLongPressEvent e) => _googleMapState.onLongPress(e.position));
GoogleMapsFlutterPlatform.instance
.onActiveLevelChanged(mapId: mapId)
.listen((MapActiveLevelChangedEvent e) =>
_googleMapState.onActiveLevelChanged(e.value));
GoogleMapsFlutterPlatform.instance
.onClusterTap(mapId: mapId)
.listen((ClusterTapEvent e) => _googleMapState.onClusterTap(e.value));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class GoogleMap extends StatefulWidget {
this.onCameraIdle,
this.onTap,
this.onLongPress,
this.onActiveLevelChanged,
this.cloudMapId,
});

Expand Down Expand Up @@ -284,6 +285,9 @@ class GoogleMap extends StatefulWidget {
/// Called every time a [GoogleMap] is long pressed.
final ArgumentCallback<LatLng>? onLongPress;

/// Called every time the active level of an indoor map changes.
final ArgumentCallback<IndoorLevel?>? onActiveLevelChanged;

/// True if a "My Location" layer should be shown on the map.
///
/// This layer includes a location indicator at the current device location,
Expand Down Expand Up @@ -635,6 +639,14 @@ class _GoogleMapState extends State<GoogleMap> {
}
}

void onActiveLevelChanged(IndoorLevel? activeLevel) {
final ArgumentCallback<IndoorLevel?>? onActiveLevelChanged =
widget.onActiveLevelChanged;
if (onActiveLevelChanged != null) {
onActiveLevelChanged(activeLevel);
}
}

void onClusterTap(Cluster cluster) {
final ClusterManager? clusterManager =
_clusterManagers[cluster.clusterManagerId];
Expand Down
16 changes: 12 additions & 4 deletions packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_maps_flutter
description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
version: 2.12.1
version: 2.13.0

environment:
sdk: ^3.6.0
Expand All @@ -21,9 +21,9 @@ flutter:
dependencies:
flutter:
sdk: flutter
google_maps_flutter_android: ^2.16.0
google_maps_flutter_ios: ^2.15.0
google_maps_flutter_platform_interface: ^2.11.0
google_maps_flutter_android: ^2.17.0
google_maps_flutter_ios: ^2.16.0
google_maps_flutter_platform_interface: ^2.12.0
google_maps_flutter_web: ^0.5.12

dev_dependencies:
Expand All @@ -33,6 +33,7 @@ dev_dependencies:
plugin_platform_interface: ^2.1.7
stream_transform: ^2.0.0


topics:
- google-maps
- google-maps-flutter
Expand All @@ -41,3 +42,10 @@ topics:
# The example deliberately includes limited-use secrets.
false_secrets:
- /example/web/index.html
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
google_maps_flutter_android: {path: ../../../packages/google_maps_flutter/google_maps_flutter_android}
google_maps_flutter_ios: {path: ../../../packages/google_maps_flutter/google_maps_flutter_ios}
google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface}
google_maps_flutter_web: {path: ../../../packages/google_maps_flutter/google_maps_flutter_web}
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,13 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform {
return mapEventStreamController.stream.whereType<MapLongPressEvent>();
}

@override
Stream<MapActiveLevelChangedEvent> onActiveLevelChanged(
{required int mapId}) {
return mapEventStreamController.stream
.whereType<MapActiveLevelChangedEvent>();
}

@override
Stream<ClusterTapEvent> onClusterTap({required int mapId}) {
return mapEventStreamController.stream.whereType<ClusterTapEvent>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,4 +579,29 @@ void main() {

expect(map.mapConfiguration.style, '');
});

testWidgets('Can listen to active level change events',
(WidgetTester tester) async {
const IndoorLevel expectedIndoorLevel =
IndoorLevel(name: '1', shortName: '1');
IndoorLevel? selectedIndoorLevel;

final GoogleMap map = GoogleMap(
initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)),
onActiveLevelChanged: (IndoorLevel? indoorLevel) {
selectedIndoorLevel = indoorLevel;
},
);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: map,
),
);

map.onActiveLevelChanged?.call(expectedIndoorLevel);

expect(selectedIndoorLevel, expectedIndoorLevel);
});
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.17.0

* Adds `onActiveLevelChanged` callback to `GoogleMap` to listen for indoor building active floor changes.

## 2.16.0

* Adds support for animating the camera with a duration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ void onClusterItemRendered(@NonNull MarkerBuilder item, @NonNull Marker marker)
@SuppressWarnings("unchecked")
private static String getClusterManagerId(Object clusterManagerData) {
Map<String, Object> clusterMap = (Map<String, Object>) clusterManagerData;
// Ref: google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart ClusterManager.toJson() method.
// Ref: google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart
// ClusterManager.toJson() method.
return (String) clusterMap.get("clusterManagerId");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.google.android.gms.maps.model.Dot;
import com.google.android.gms.maps.model.Gap;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.IndoorLevel;
import com.google.android.gms.maps.model.JointType;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
Expand Down Expand Up @@ -325,7 +326,8 @@ static CameraUpdate cameraUpdateFromPigeon(Messages.PlatformCameraUpdate update,
return (zoom.getOut()) ? CameraUpdateFactory.zoomOut() : CameraUpdateFactory.zoomIn();
}
throw new IllegalArgumentException(
"PlatformCameraUpdate's cameraUpdate field must be one of the PlatformCameraUpdate... case classes.");
"PlatformCameraUpdate's cameraUpdate field must be one of the PlatformCameraUpdate... case"
+ " classes.");
}

private static double toDouble(Object o) {
Expand Down Expand Up @@ -408,6 +410,13 @@ static LatLng latLngFromPigeon(Messages.PlatformLatLng latLng) {
return new LatLng(latLng.getLatitude(), latLng.getLongitude());
}

static Messages.PlatformIndoorLevel indoorLevelToPigeon(IndoorLevel indoorLevel) {
return new Messages.PlatformIndoorLevel.Builder()
.setName(indoorLevel.getName())
.setShortName(indoorLevel.getShortName())
.build();
}

static Messages.PlatformCluster clusterToPigeon(
String clusterManagerId, Cluster<MarkerBuilder> cluster) {
int clusterSize = cluster.getSize();
Expand Down Expand Up @@ -961,7 +970,8 @@ static Tile tileFromPigeon(Messages.PlatformTile tile) {
@NonNull GroundOverlay groundOverlay) {
Messages.PlatformDoublePair.Builder anchorBuilder = new Messages.PlatformDoublePair.Builder();

// Position is overlays anchor point. Calculate normalized anchor point based on position and bounds.
// Position is overlays anchor point. Calculate normalized anchor point based on position and
// bounds.
LatLng position = groundOverlay.getPosition();
LatLngBounds bounds = groundOverlay.getBounds();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.googlemaps;

import androidx.annotation.NonNull;
import com.google.android.gms.maps.model.IndoorLevel;
import io.flutter.plugins.googlemaps.Messages.MapsCallbackApi;

class FloorController {
private final @NonNull MapsCallbackApi flutterApi;

FloorController(@NonNull Messages.MapsCallbackApi flutterApi) {
this.flutterApi = flutterApi;
}

void onActiveLevelChanged(IndoorLevel indoorLevel) {
flutterApi.onActiveLevelChanged(
indoorLevel == null ? null : Convert.indoorLevelToPigeon(indoorLevel),
new NoOpVoidResult());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.IndoorBuilding;
import com.google.android.gms.maps.model.IndoorLevel;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapStyleOptions;
Expand Down Expand Up @@ -67,6 +69,7 @@ class GoogleMapController
MapsApi,
MapsInspectorApi,
OnMapReadyCallback,
GoogleMap.OnIndoorStateChangeListener,
PlatformView {

private static final String TAG = "GoogleMapController";
Expand All @@ -93,6 +96,7 @@ class GoogleMapController
private final PolygonsController polygonsController;
private final PolylinesController polylinesController;
private final CirclesController circlesController;
private final FloorController floorController;
private final HeatmapsController heatmapsController;
private final TileOverlaysController tileOverlaysController;
private final GroundOverlaysController groundOverlaysController;
Expand Down Expand Up @@ -139,6 +143,7 @@ class GoogleMapController
this.polygonsController = new PolygonsController(flutterApi, density);
this.polylinesController = new PolylinesController(flutterApi, assetManager, density);
this.circlesController = new CirclesController(flutterApi, density);
this.floorController = new FloorController(flutterApi);
this.heatmapsController = new HeatmapsController();
this.tileOverlaysController = new TileOverlaysController(flutterApi);
this.groundOverlaysController = new GroundOverlaysController(flutterApi, assetManager, density);
Expand All @@ -158,6 +163,7 @@ class GoogleMapController
PolygonsController polygonsController,
PolylinesController polylinesController,
CirclesController circlesController,
FloorController floorController,
HeatmapsController heatmapController,
TileOverlaysController tileOverlaysController,
GroundOverlaysController groundOverlaysController) {
Expand All @@ -166,6 +172,7 @@ class GoogleMapController
this.binaryMessenger = binaryMessenger;
this.flutterApi = flutterApi;
this.options = options;
this.floorController = floorController;
this.mapView = new MapView(context, options);
this.density = context.getResources().getDisplayMetrics().density;
this.lifecycleProvider = lifecycleProvider;
Expand Down Expand Up @@ -383,6 +390,20 @@ public void onGroundOverlayClick(@NonNull GroundOverlay groundOverlay) {
groundOverlaysController.onGroundOverlayTap(groundOverlay.getId());
}

@Override
public void onIndoorBuildingFocused() {
if (googleMap != null) {
IndoorBuilding currentBuilding = googleMap.getFocusedBuilding();
floorController.onActiveLevelChanged(currentBuilding != null ? currentBuilding.getLevels().get(currentBuilding.getActiveLevelIndex()) : null);
}
}

@Override
public void onIndoorLevelActivated(IndoorBuilding building) {
IndoorLevel activeLevel = building.getLevels().get(building.getActiveLevelIndex());
floorController.onActiveLevelChanged(activeLevel);
}

@Override
public void dispose() {
if (disposed) {
Expand Down Expand Up @@ -416,6 +437,7 @@ private void setGoogleMapListener(@Nullable GoogleMapListener listener) {
googleMap.setOnMapClickListener(listener);
googleMap.setOnMapLongClickListener(listener);
googleMap.setOnGroundOverlayClickListener(listener);
googleMap.setOnIndoorStateChangeListener(listener);
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ interface GoogleMapListener
GoogleMap.OnMapClickListener,
GoogleMap.OnMapLongClickListener,
GoogleMap.OnMarkerDragListener,
GoogleMap.OnGroundOverlayClickListener {}
GoogleMap.OnGroundOverlayClickListener,
GoogleMap.OnIndoorStateChangeListener {}
Loading