Repository navigation
[google_maps_flutter] Convert top-level classes to Swift in _sdk* packages - #12307
Conversation
There was a problem hiding this comment.
Code Review
This pull request converts parts of the iOS Google Maps implementation to Swift across the SDK 9, SDK 10, and shared code packages, replacing several Objective-C controllers and plugins. The build configurations, synchronization tools, and tests are updated to support the new Swift files. Feedback on these changes includes correcting a regular expression typo in the utility script, addressing typos in documentation and comments, avoiding unsafe force-unwrapping and force-casting in the map factory, and resolving an inconsistency in how uninitialized map views are handled.
| -> NSNumber? | ||
| { | ||
| guard let mapView = controller?.mapView else { | ||
| return false |
There was a problem hiding this comment.
There was a problem hiding this comment.
This is intentional; if the map isn't present, it doesn't support advanced markers.
There was a problem hiding this comment.
Is this a Swift thing? Like the return type is NSNumber?, but this is returning false. Does that automatically become 0?
There was a problem hiding this comment.
It automatically becomes an NSNumber whose boolValue is false; Swift will essentially auto-box literals that are in an NSNumber context (vs Obj-C where we would have to write @(NO)).
|
We'll want to hold off on landing this until 3.47 reaches stable, since that's when nobody on stable will have an actual need to keep using @vashworth per offline discussion, this punts on the question of whether we switch the endorsed implementation to For review, it may be helpful to look at some of the individual commits. 5a27e87 in particular, which is where I reworked the initialization. |
| #expect(mapView.frameObserverCount == 0) | ||
| } | ||
|
|
||
| @Test func mapsServiceSync() { |
There was a problem hiding this comment.
This is removed because in the Swift version, all the custom logic for calling this exactly once was replaced by us being able to use Swift's lazy static handling, and since that's a core language behavior rather than custom logic, there's no value in testing it.
| ) | ||
| } | ||
|
|
||
| @Test func frameObserverRemovedOnDeinitIfNeverFired() { |
There was a problem hiding this comment.
Added to cover an issue found by Gemini in local review of the conversion.
| #expect(mapView.frameObserverCount == 0) | ||
| } | ||
|
|
||
| @Test func styleErrorPersistsAcrossConfigUpdates() { |
There was a problem hiding this comment.
Added to cover a case I accidentally regressed when reworking init, also found by Gemini in a local review.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
| s.xcconfig = { | ||
| 'LIBRARY_SEARCH_PATHS' => '$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', | ||
| 'LD_RUNPATH_SEARCH_PATHS' => '$(inherited) /usr/lib/swift', | ||
| 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', |
There was a problem hiding this comment.
Why did $(inherited) need to be removed?
There was a problem hiding this comment.
Our tooling's regex didn't handle it, and it seemed easier to make this match all of our other plugins than to change the tooling (given that I don't know of any reason this plugin would need to be different).
|
|
||
| mapView.delegate = self | ||
| isObservingFrame = true | ||
| mapView.addObserver(self, forKeyPath: "frame", options: [], context: nil) |
There was a problem hiding this comment.
In ObjC, options is 0, I'm assuming [] is the swift equivalent? Is the API different in swift?
There was a problem hiding this comment.
Yes, this is the very nice auto-bridging from NS_OPTIONS to https://developer.apple.com/documentation/swift/optionset.
| if styleUpdateAttempted { | ||
| styleError = errorString | ||
| } | ||
| if let trackCameraPosition = config.trackCameraPosition { | ||
| self.trackCameraPosition = trackCameraPosition.boolValue | ||
| } |
There was a problem hiding this comment.
Question: Why does trackCameraPosition need to be accessed via self, but styleError doesn't?
There was a problem hiding this comment.
Like Dart, in Swift the convention is to only use self when necessary to disambiguate from a local. Line 39 creates a local that shadows the ivar, but nothing shadows styleError.
There was a problem hiding this comment.
Ah I see, thanks for explaining!
|
|
||
| class MapCallHandler: NSObject, FGMMapsApi { | ||
| weak var controller: GoogleMapController? | ||
| let messenger: FlutterBinaryMessenger |
There was a problem hiding this comment.
In ObjC, this uses copy, is this going to maintain a strong reference?
There was a problem hiding this comment.
copy was always wrong and only really worked by accident 😬 Strong refs to the messenger should be fine; we use a proxy object with an internal weak ref in the engine to avoid retain loops with the engine.
| controller?.markersController.add(toAdd) | ||
| controller?.markersController.change(toChange) | ||
| controller?.markersController.removeMarkers(withIdentifiers: idsToRemove) | ||
| controller?.clusterManagersController.invokeClusteringForEachClusterManager() |
There was a problem hiding this comment.
Add back comment
| controller?.clusterManagersController.invokeClusteringForEachClusterManager() | |
| // Invoke clustering after markers are added. | |
| controller?.clusterManagersController.invokeClusteringForEachClusterManager() |
There was a problem hiding this comment.
Thanks, I thought I caught all of these and added the back. For some reason, Gemini loves removing implementation comments when doing conversions to Swift.
| weak var controller: GoogleMapController? | ||
| let messenger: FlutterBinaryMessenger | ||
| let pigeonSuffix: String | ||
| var transactionWrapper: FGMCATransactionProtocol |
There was a problem hiding this comment.
In ObjC, this is in a test header? Do we not need to do that in Swift?
There was a problem hiding this comment.
This class isn't public, so we shouldn't need to worry about visibility of its members.
| // initialization. | ||
| var styleError: String? | ||
| /// Whether we are currently observing the "frame" key path on `mapView`. | ||
| private var isObservingFrame = false |
There was a problem hiding this comment.
This seems like new logic. It doesn't appear to be part of the self/init fixes. Is this fixing an issue? Perhaps add a comment somewhere on why it's needed
There was a problem hiding this comment.
Yes, this is fixing an issue the local Gemini review flagged. We were removing the observer when it fired the first time, but if it never did we didn't remove the observer on dealloc, which is a potentially serious bug (since unlike notification center observers, docs don't indicate that Apple made this automagically safe). Added a better comment.
| ) | ||
| } | ||
|
|
||
| public init( |
There was a problem hiding this comment.
I don't think this was public in the Objc version? It was in a Test category
There was a problem hiding this comment.
Good catch! I didn't even register that in my review since public init seems natural.
| SetUpFGMMapsInspectorApiWithSuffix(inspector.messenger, nil, inspector.pigeonSuffix) | ||
| } | ||
|
|
||
| public func view() -> UIView { |
There was a problem hiding this comment.
Is this intentionally public? I don't think it was public in objc
There was a problem hiding this comment.
As far as I can tell there's no such thing in Swift as non-public conformance to a public protocol, and as currently architected, GoogleMapController is the FlutterPlatformView, so must implement this method.
If you want, I can separate out a minimal GoogleMapPluginView to implement FlutterPlatformView and then make that create and own GoogleMapController. It would still need to expose this, but it would be on a different object with a clearer scope.
| return mapView | ||
| } | ||
|
|
||
| override public func observeValue( |
There was a problem hiding this comment.
Is this intentionally public?
There was a problem hiding this comment.
It has to be, because the NSObject method is public.
|
LGTM, but tests are failing on formatting stuff I think |
|
Yep, I forgot to check for formatter behavior changes due to the min SDK bump. |
|
Actually, I can just revert the min SDK bumps; these packages already required higher versions of iOS, so we don't need to bind the implementation changes in them to the SDK. I was just thinking about the overall change of freezing the SDK 8 version being something we needed to wait for 3.47 for, and then translated that into "bump the min SDK" in my mind at some point. The only package where we would need an SDK bump would be the app-facing package, if/when we switch to |
…ackages (flutter#12307) This converts GoogleMapsPlugin, GoogleMapFactory, and GoogleMapController to Swift, and restructures the source code to typical migration state of having the Swift code be the primary target, depending on a new `*_objc` target that contains the unmigrated code. Because `google_maps_flutter_ios` cannot adopt Swift (see discussion in linked issue), this removes that package from the code sharing. Going forward, development will be limited to the `_sdk*` packages. For reasons explained [here](flutter/flutter#119108 (comment)), the Pigeon API layer is still Objective-C for now, which does lead to some temporary non-ideal code in GoogleMapController. That will be cleaned up in the last step when we are able to switch to Swift Pigeon generation. The remaining Obj-C code will be migrated in a series of follow-up PRs to keep the scope of each PR reasonable for review. The conversion process was: - Initial conversion via Gemini, with explicit instruction to keep the structure the same. - Side-by-side manual review of the old and new versions of the files. - Manual fixes and improvements. The last step was more extensive than usual because the init method had longstanding design issues (flutter/flutter#104121) that became hard errors under Swift. There was some refactoring of helper methods and reordering of initialization steps to make the code work with the strict two-phase init process. Part of flutter/flutter#119108 Fixes flutter/flutter#104121 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
…r#191886) flutter/packages@740f093...bd3cbc1 2026-08-26 engine-flutter-autoroll@skia.org Roll Flutter from 9a82789 to 15d8908 (37 revisions) (flutter/packages#12619) 2026-08-26 stuartmorgan@google.com [google_maps_flutter] Convert simple object controllers to Swift (flutter/packages#12618) 2026-08-26 21270878+elliette@users.noreply.github.com [material_ui] `gen_defaults` should follow new versioning strategy (flutter/packages#12627) 2026-08-25 10687576+bparrishMines@users.noreply.github.com [material_ui] Fixes analysis_options migration from calling `flutter pub get` on pacakage (flutter/packages#12626) 2026-08-25 jason-simmons@users.noreply.github.com [material_ui] Declare Flutter SDK dependencies in the pubspec.yaml for tool/gen_defaults (flutter/packages#12623) 2026-08-25 21270878+elliette@users.noreply.github.com [material_ui] Set-up `tool/gen_defaults` sub-directory (flutter/packages#12469) 2026-08-25 stuartmorgan@google.com [google_maps_flutter] Convert top-level classes to Swift in `_sdk*` packages (flutter/packages#12307) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…ackages (flutter#12307) This converts GoogleMapsPlugin, GoogleMapFactory, and GoogleMapController to Swift, and restructures the source code to typical migration state of having the Swift code be the primary target, depending on a new `*_objc` target that contains the unmigrated code. Because `google_maps_flutter_ios` cannot adopt Swift (see discussion in linked issue), this removes that package from the code sharing. Going forward, development will be limited to the `_sdk*` packages. For reasons explained [here](flutter/flutter#119108 (comment)), the Pigeon API layer is still Objective-C for now, which does lead to some temporary non-ideal code in GoogleMapController. That will be cleaned up in the last step when we are able to switch to Swift Pigeon generation. The remaining Obj-C code will be migrated in a series of follow-up PRs to keep the scope of each PR reasonable for review. The conversion process was: - Initial conversion via Gemini, with explicit instruction to keep the structure the same. - Side-by-side manual review of the old and new versions of the files. - Manual fixes and improvements. The last step was more extensive than usual because the init method had longstanding design issues (flutter/flutter#104121) that became hard errors under Swift. There was some refactoring of helper methods and reordering of initialization steps to make the code work with the strict two-phase init process. Part of flutter/flutter#119108 Fixes flutter/flutter#104121 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
This converts GoogleMapsPlugin, GoogleMapFactory, and GoogleMapController to Swift, and restructures the source code to typical migration state of having the Swift code be the primary target, depending on a new
*_objctarget that contains the unmigrated code. Becausegoogle_maps_flutter_ioscannot adopt Swift (see discussion in linked issue), this removes that package from the code sharing. Going forward, development will be limited to the_sdk*packages.For reasons explained here, the Pigeon API layer is still Objective-C for now, which does lead to some temporary non-ideal code in GoogleMapController. That will be cleaned up in the last step when we are able to switch to Swift Pigeon generation.
The remaining Obj-C code will be migrated in a series of follow-up PRs to keep the scope of each PR reasonable for review.
The conversion process was:
The last step was more extensive than usual because the init method had longstanding design issues (flutter/flutter#104121) that became hard errors under Swift. There was some refactoring of helper methods and reordering of initialization steps to make the code work with the strict two-phase init process.
Part of flutter/flutter#119108
Fixes flutter/flutter#104121
Pre-Review Checklist
[shared_preferences]///).Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2