Sync upstream location 10.x and keep STEM headless fixes - #6
Conversation
bhavik-dodia-aub
commented
Jul 30, 2026
- Sync with upstream flutter_location (incl. modern Android/iOS rewrite and SPM-ready layout)
- Keep STEM behaviour: SharedEngine-only binding and activity-independent location for CarPlay / Android Auto / headless
- Drop fork-only work already covered upstream (e.g. UIAlertController / null-safety)
…nitialValue `value:` was deprecated after Flutter 3.33 in favour of `initialValue:`. Restores a clean `flutter analyze --fatal-infos` across all packages.
Updates dev dependencies to their latest majors across location, location_platform_interface and location_web. leancode_lint 24 flagged one new lint (unnecessary_async on Location.getLocation), fixed here so it matches its sibling forwarders.
The example's root build.gradle declared no repositories for its subprojects, so Gradle could only resolve dependencies from Flutter's storage repo and failed to find AndroidX transitive artifacts (androidx.core, window-java, exifinterface, relinker). Adding the google()/mavenCentral() repositories lets the example build again.
… 12.1.2 Flutter 3.44 pulls androidx.core 1.17.0, which requires AGP >= 8.9.1, and Flutter warns that support for AGP < 8.11.1 and Kotlin < 2.2.20 will be dropped. Bumps the plugin and example toolchain accordingly; ktlint-gradle goes to 12.1.2 for Kotlin 2.2 compatibility. Example builds cleanly with no remaining toolchain warnings.
… deps Aligns the plugin with Flutter 3.44 (compileSdk/targetSdk 36) and bumps core-ktx to 1.16.0. androidx.annotation stays at 1.8.1 to satisfy Gradle consistent resolution with the transitive graph pulled by core 1.16.0.
Runs Flutter 3.44's automatic project migrations on the example: - iOS moves to the UIScene app lifecycle (UIApplicationSceneManifest, FlutterSceneDelegate, implicit-engine AppDelegate) and an iOS 13 floor. - Android gains the builtInKotlin/newDsl migrator flags. Quiets the build-time deprecation warnings and keeps the example on current templates. Background location mode is preserved.
Converts LocationPlugin, MethodCallHandlerImpl and StreamHandlerImpl to idiomatic Kotlin and moves all Kotlin sources under src/main/kotlin so Flutter's plugin loader resolves the main class. FlutterLocation stays Java for now (converted next). - Null-safe teardown in LocationPlugin.dispose()/detachActivity() so a detach after the engine already detached no longer throws NPE (Lyokone#1041). - MethodCallHandlerImpl guards a null location with an error instead of crashing when a call arrives before the service is bound. - Renames the notification default consts to SCREAMING_SNAKE_CASE to satisfy ktlint 12.
…ion APIs Converts the last Java source, FlutterLocation, to Kotlin and moves off the deprecated FusedLocationProvider APIs: - LocationRequest is now built with LocationRequest.Builder and com.google.android.gms.location.Priority constants instead of the deprecated create()/setPriority()/PRIORITY_* API (Lyokone#1019, Lyokone#1023, Lyokone#1035). - LocationResult.lastLocation is null-checked (it is now nullable). - Mock detection uses Location.isMock on API 31+ and falls back to the deprecated isFromMockProvider below it (Lyokone#1016). - Drops the dead pre-API-21 branches (minSdk is 21). Behaviour is otherwise preserved. Example builds cleanly and ktlint passes.
Android 14 (API 34) throws when a foreground service starts without declaring a type. Uses ServiceCompat.startForeground with FOREGROUND_SERVICE_TYPE_LOCATION on API 29+ and declares the FOREGROUND_SERVICE / FOREGROUND_SERVICE_LOCATION permissions in the plugin manifest so background location works on modern Android (Lyokone#970).
Replaces the Objective-C LocationPlugin (duplicated across ios/, macos/ and an unused darwin/) with a single Swift implementation under darwin/, wired through Flutter's sharedDarwinSource so both platforms compile the same file. A single darwin/location.podspec replaces the two stub podspecs (which still read "A new flutter plugin project" / "Your Company") and sets real metadata, iOS 12 / macOS 10.15 floors and swift_version 5.0. Modernizations while porting: - Uses the instance authorizationStatus and locationManagerDidChangeAuthorization on iOS 14 / macOS 11+, falling back to the deprecated class APIs below that. - Drops UIAlertView (removed from modern SDKs) in favour of opening Settings. - Replaces the NSException hard-crash on a missing usage description with a logged warning, so a misconfigured Info.plist no longer crashes at launch (Lyokone#1040, Lyokone#1042). - Guards every FlutterResult callback against double or nil invocation. Both example apps build for the iOS simulator and macOS.
Flutter 3.44 migrates the macOS example: minimum deployment target 10.15, @NSApplicationMain to @main, applicationSupportsSecureRestorableState, and the Xcode-compatibility pbxproj/scheme updates. Applied by building the macOS example against the new Swift plugin.
Adds federated desktop support to the location plugin: - Windows: a C++/WinRT plugin backed by Windows.Devices.Geolocation. getLocation and the location stream use Geolocator, permission maps to RequestAccessAsync, and results are marshalled back to the platform thread via apartment_context. Background mode is unsupported. - Linux: a GObject plugin talking to GeoClue2 over GDBus (GIO), so no extra runtime dependency beyond glib/gio. Implements getLocation (answered on the next LocationUpdated), the stream, and service availability. Both are registered in the plugin pubspec. They compile-verify in CI (added separately) but still need on-device runtime testing.
Scaffolds the example's Linux and Windows runners and adds a location-desktop workflow that compiles the example on ubuntu-latest and windows-latest, giving the new native plugins CI coverage. Also adds a macOS build job to location-prepare and drops the stale clang-format step that pointed at the now-removed Objective-C ios/ sources.
The prepare-flutter job pinned Flutter 3.27.x, which predates the DropdownButtonFormField.initialValue API the example now uses, and the android/ios jobs referenced an undefined matrix version. Switches every job to channel: stable and the latest Melos.
…S) location Android (Lyokone#990/Lyokone#991): request both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION, treat approximate-only grants as granted in checkPermissions()/shouldShowRequestPermissionRationale() and in the permission-result handler, so choosing "Approximate" no longer reports denied. NMEA/MSL altitude is only registered when precise access is held. iOS (Lyokone#984): when precise location is off the system delivers a single reduced-accuracy update; skip the stale-location guard in that case so getLocation still resolves.
C++/WinRT co_await on IAsyncOperation needs the C++20 <coroutine> header; under C++17 MSVC fell back to the now-removed <experimental/coroutine> and failed to compile. Sets the plugin target to C++20.
…able Adds a supported-platforms table (Android, iOS, macOS, web, Windows, Linux), Windows and Linux setup notes, and bumps the pubspec install snippet to ^8.0.0.
…e#1047) Moves the shared Swift source into a SwiftPM package layout (darwin/location/Sources/location) and adds Package.swift, so the plugin is consumable via Swift Package Manager. The podspec is updated to the new path and CocoaPods keeps working, so both integration paths are supported. Verified: the iOS and macOS example apps build both with SwiftPM enabled and with the classic CocoaPods flow.
MSVC rejects co_await inside a catch block. Records the error message in the handler and responds after the try/catch instead.
Flutter's generated plugin registrant includes <location/location_plugin.h> and calls LocationPluginRegisterWithRegistrar, but the plugin exposed the _c_api variant. Consolidates the class and the C entry point into location_plugin.cpp behind include/location/location_plugin.h with the expected LocationPluginRegisterWithRegistrar symbol.
Major maintenance release: Kotlin/Swift rewrites, Windows and Linux support, Swift Package Manager, modernized toolchain and a batch of issue fixes. See CHANGELOG.md for the full list.
…nux, SPM Full modernization: Kotlin + Swift rewrite, dependency bumps, Windows/Linux support
CLLocationManager.locationServicesEnabled() can block the calling thread while location services start up. Apple warns against calling it on the main thread; doing so triggered the "UI unresponsiveness" runtime warning and could hang the app (Lyokone#782, Lyokone#789, Lyokone#909, Lyokone#1004, Lyokone#1027). Run the check on a background queue and deliver the result back on the main thread, where the CLLocationManager instance and the FlutterResult must be used. getLocation, serviceEnabled, requestService and changeSettings no longer stall the UI.
…ices-off-main-thread fix(darwin): move locationServicesEnabled off the main thread
Apple requires third-party SDKs to ship a PrivacyInfo.xcprivacy declaring their data use and any required-reason API access. This plugin only bridges CoreLocation to the host app: it transmits nothing off-device, uses no required-reason APIs, and does no tracking. The manifest declares exactly that (all empty / false). Bundled through both build systems: resource_bundles in the podspec for CocoaPods, and a processed resource in Package.swift for Swift Package Manager. Fixes Lyokone#947
…fest feat(darwin): add Apple privacy manifest
getLocation() could never complete when Core Location delivered fewer than three updates. The stale-location guard swallowed the first two updates by count to skip the cached "last known" fix, so a one-shot getLocation() only resolved on the third update. When few updates arrive — a static iOS-simulator "Custom Location" (Lyokone#657, Lyokone#955, Lyokone#1005, Lyokone#1013), reduced accuracy which emits a single update (Lyokone#984), or a sparse first fix (Lyokone#798) — the Dart Future hung forever with no error. Skip stale fixes by age instead of by count: deliver the first update whose timestamp is recent, which resolves getLocation() immediately while still ignoring the instantly-delivered cached location. Verified on an iOS simulator with a static Custom Location — an integration test that hangs (20s timeout) on the old code passes on the new code. Fixes Lyokone#798, fixes Lyokone#955, fixes Lyokone#1005, fixes Lyokone#657, fixes Lyokone#1013
…tale-guard-hang fix(darwin): stop getLocation hanging on sparse location updates
The feature docs described an API that was never shipped (the abandoned v5
redesign): getLocation({LocationSettings? settings}), setLocationSettings(...),
a top-level getLocation()/onLocationChanged({inBackground}), getPermissionStatus,
requestPermissions, isGPSEnabled/isNetworkEnabled, updateBackgroundNotification
and the authorizedAlways/authorizedWhenInUse/notDetermined statuses. None of
these exist in the published package, so every code sample on the docs site
failed to compile.
Rewrite the examples and signatures against the real API, using package source
and the example app as the source of truth:
- getLocation() takes no arguments; configure via changeSettings.
- onLocationChanged is a Stream getter; background is enableBackgroundMode.
- Settings are global via changeSettings(accuracy/interval/distanceFilter/
pausesLocationUpdatesAutomatically), not setLocationSettings.
- Permissions use hasPermission()/requestPermission() and the
granted/grantedLimited/denied/deniedForever statuses.
- Services use serviceEnabled()/requestService().
- Notification uses changeNotificationOptions (fixes a stray
changeBackgroundOptions call).
- All snippets call the API on a Location instance; bump getting-started to
location: ^9.0.0.
docs: revamp root README into a landing page
…tion Upstream 10.x rewrote Android in Kotlin and binds the location service to an Activity again. Restore the STEMLogic fork behaviors that upstream does not cover: only attach against SharedEngine (avoid Firebase's background engine), bind the service from application context, and keep fused location alive when no Activity is present for headless / vehicle platforms.
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 |
| name: Linux | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Clone repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 | ||
| with: | ||
| channel: stable | ||
|
|
||
| - name: Install Linux build dependencies | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev | ||
|
|
||
| - name: Set up Melos | ||
| run: dart pub global activate melos | ||
|
|
||
| - name: melos bootstrap | ||
| run: melos bootstrap | ||
|
|
||
| - name: Build example app | ||
| working-directory: packages/location/example | ||
| run: flutter build linux --debug | ||
|
|
||
| build-windows: |
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 |
| name: Windows | ||
| runs-on: windows-latest | ||
|
|
||
| steps: | ||
| - name: Clone repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 | ||
| with: | ||
| channel: stable | ||
|
|
||
| - name: Set up Melos | ||
| run: dart pub global activate melos | ||
|
|
||
| - name: melos bootstrap | ||
| run: melos bootstrap | ||
|
|
||
| - name: Build example app | ||
| working-directory: packages/location/example | ||
| run: flutter build windows --debug |
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 |
There was a problem hiding this comment.
Pull request overview
Syncs the fork with upstream Location 10.x while retaining SharedEngine/headless behavior and adding desktop support.
Changes:
- Modernizes Android and shared Darwin implementations.
- Adds Linux/Windows plugins and example runners.
- Expands APIs, documentation, tests, and CI for new platforms and features.
Reviewed changes
Copilot reviewed 116 out of 119 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
README.md |
Refreshes project overview and quick start. |
packages/location/windows/location_plugin.cpp |
Implements Windows location support. |
packages/location/windows/include/location/location_plugin.h |
Declares Windows plugin registration. |
packages/location/windows/CMakeLists.txt |
Configures the Windows plugin build. |
packages/location/test/location_test.mocks.dart |
Regenerates Location mocks. |
packages/location/test/location_test.dart |
Tests last-known-location delegation. |
packages/location/README.md |
Documents platforms and expanded API. |
packages/location/pubspec.yaml |
Bumps version and registers desktop/Darwin platforms. |
packages/location/macos/location.podspec |
Removes obsolete macOS podspec. |
packages/location/macos/Classes/LocationPlugin.h |
Removes obsolete macOS header. |
packages/location/linux/location_plugin.cc |
Implements Linux GeoClue support. |
packages/location/linux/include/location/location_plugin.h |
Declares Linux plugin registration. |
packages/location/linux/CMakeLists.txt |
Configures the Linux plugin build. |
packages/location/lib/location.dart |
Exposes expanded platform APIs. |
packages/location/ios/location.podspec |
Removes obsolete iOS podspec. |
packages/location/ios/Classes/LocationPlugin.h |
Removes obsolete iOS header. |
packages/location/ios/Assets/.gitkeep |
Retains the iOS assets directory. |
packages/location/darwin/location/Sources/location/LocationPlugin.swift |
Provides shared Apple implementation. |
packages/location/darwin/location/Sources/location/PrivacyInfo.xcprivacy |
Adds Apple privacy metadata. |
packages/location/darwin/location/Package.swift |
Adds Swift Package support. |
packages/location/darwin/location/.gitignore |
Ignores SwiftPM artifacts. |
packages/location/darwin/location.podspec |
Adds shared Darwin CocoaPods support. |
packages/location/darwin/Classes/LocationPlugin.h |
Removes obsolete Darwin header. |
packages/location/android/src/main/kotlin/com/lyokone/location/FlutterLocation.kt |
Rewrites Android location handling. |
packages/location/android/src/main/kotlin/com/lyokone/location/FlutterLocationService.kt |
Rewrites Android foreground service handling. |
packages/location/android/src/main/kotlin/com/lyokone/location/StreamHandlerImpl.kt |
Implements Android stream dispatch. |
packages/location/android/src/main/kotlin/com/lyokone/location/MethodCallHandlerImpl.kt |
Implements Android method dispatch. |
packages/location/android/src/main/kotlin/com/lyokone/location/LocationPlugin.kt |
Adds SharedEngine-aware plugin binding. |
packages/location/android/src/main/java/com/lyokone/location/StreamHandlerImpl.java |
Removes legacy Java stream handler. |
packages/location/android/src/main/java/com/lyokone/location/MethodCallHandlerImpl.java |
Removes legacy Java method handler. |
packages/location/android/src/main/java/com/lyokone/location/LocationPlugin.java |
Removes legacy Java plugin. |
packages/location/android/src/main/java/com/lyokone/location/FlutterLocationService.kt |
Removes old service location. |
packages/location/android/src/main/java/com/lyokone/location/FlutterLocation.java |
Removes old Java location implementation. |
packages/location/android/src/main/AndroidManifest.xml |
Updates Android service permissions/configuration. |
packages/location/android/build.gradle |
Modernizes Android and Kotlin builds. |
packages/location/example/windows/runner/win32_window.h |
Adds Windows window abstraction. |
packages/location/example/windows/runner/win32_window.cpp |
Implements the Windows window abstraction. |
packages/location/example/windows/runner/utils.h |
Declares Windows runner utilities. |
packages/location/example/windows/runner/utils.cpp |
Implements Windows runner utilities. |
packages/location/example/windows/runner/Runner.rc |
Adds Windows application resources. |
packages/location/example/windows/runner/runner.exe.manifest |
Adds Windows compatibility metadata. |
packages/location/example/windows/runner/resource.h |
Defines Windows resource identifiers. |
packages/location/example/windows/runner/main.cpp |
Adds Windows runner entry point. |
packages/location/example/windows/runner/flutter_window.h |
Declares the Flutter host window. |
packages/location/example/windows/runner/flutter_window.cpp |
Implements the Flutter host window. |
packages/location/example/windows/runner/CMakeLists.txt |
Configures the Windows runner. |
packages/location/example/windows/flutter/generated_plugins.cmake |
Registers Windows plugins. |
packages/location/example/windows/flutter/generated_plugin_registrant.h |
Declares Windows plugin registration. |
packages/location/example/windows/flutter/CMakeLists.txt |
Configures Flutter’s Windows build. |
packages/location/example/windows/CMakeLists.txt |
Configures the Windows example. |
packages/location/example/windows/.gitignore |
Ignores Windows build artifacts. |
packages/location/example/web/index.html |
Adds configurable web base path. |
packages/location/example/pubspec.yaml |
Adds Flutter test dependency. |
packages/location/example/macos/Runner/AppDelegate.swift |
Modernizes macOS application startup. |
packages/location/example/macos/Runner.xcworkspace/contents.xcworkspacedata |
Adds the macOS workspace. |
packages/location/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme |
Updates macOS scheme settings. |
packages/location/example/macos/Runner.xcodeproj/project.pbxproj |
Modernizes the macOS project. |
packages/location/example/macos/Podfile |
Raises macOS deployment target. |
packages/location/example/linux/runner/my_application.h |
Declares the Linux example application. |
packages/location/example/linux/runner/my_application.cc |
Implements the Linux example application. |
packages/location/example/linux/runner/main.cc |
Adds Linux runner entry point. |
packages/location/example/linux/runner/CMakeLists.txt |
Configures the Linux runner. |
packages/location/example/linux/flutter/generated_plugins.cmake |
Registers Linux plugins. |
packages/location/example/linux/flutter/generated_plugin_registrant.h |
Declares Linux plugin registration. |
packages/location/example/linux/flutter/CMakeLists.txt |
Configures Flutter’s Linux build. |
packages/location/example/linux/CMakeLists.txt |
Configures the Linux example. |
packages/location/example/linux/.gitignore |
Ignores Linux build artifacts. |
packages/location/example/lib/service_enabled.dart |
Adds stable service-control keys. |
packages/location/example/lib/permission_status.dart |
Adds stable permission-control keys. |
packages/location/example/lib/listen_location.dart |
Improves stream teardown and testability. |
packages/location/example/lib/get_location.dart |
Demonstrates last-known-location retrieval. |
packages/location/example/lib/enable_in_background.dart |
Adds background-control keys. |
packages/location/example/lib/change_settings.dart |
Demonstrates background intervals. |
packages/location/example/lib/change_notification.dart |
Updates dropdown initialization. |
packages/location/example/ios/Runner/Info.plist |
Modernizes iOS scene configuration. |
packages/location/example/ios/Runner/AppDelegate.swift |
Registers plugins with implicit engines. |
packages/location/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme |
Updates iOS build/debug setup. |
packages/location/example/ios/Runner.xcodeproj/project.pbxproj |
Adds Swift Package plugin integration. |
packages/location/example/ios/Podfile |
Updates documented iOS target. |
packages/location/example/ios/Flutter/AppFrameworkInfo.plist |
Defers minimum OS metadata. |
packages/location/example/android/settings.gradle |
Updates Android build plugins. |
packages/location/example/android/gradle/wrapper/gradle-wrapper.properties |
Updates Gradle. |
packages/location/example/android/gradle.properties |
Adds Flutter migration flags. |
packages/location/example/android/build.gradle |
Adds dependency repositories. |
packages/location/example/.metadata |
Records Flutter migration metadata. |
packages/location/example/.gitignore |
Ignores test artifacts. |
packages/location_web/README.md |
Fixes the package link. |
packages/location_web/pubspec.yaml |
Bumps web package dependencies/version. |
packages/location_web/lib/location_web.dart |
Expands web permission and error handling. |
packages/location_web/CHANGELOG.md |
Documents web 7.0 changes. |
packages/location_platform_interface/test/types_test.dart |
Tests serialization and accessory metadata. |
packages/location_platform_interface/test/method_channel_location_test.dart |
Tests new channel methods. |
packages/location_platform_interface/test/location_platform_interface_test.dart |
Tests default API behavior. |
packages/location_platform_interface/pubspec.yaml |
Bumps platform-interface dependencies/version. |
packages/location_platform_interface/lib/src/types.dart |
Expands and serializes LocationData. |
packages/location_platform_interface/lib/src/method_channel_location.dart |
Adds new method-channel APIs. |
packages/location_platform_interface/lib/location_platform_interface.dart |
Expands the public platform contract. |
packages/location_platform_interface/CHANGELOG.md |
Documents platform-interface 7.0. |
melos.yaml |
Scopes unit-test discovery. |
docs/installation/windows.mdx |
Adds Windows installation guidance. |
docs/installation/linux.mdx |
Adds Linux installation guidance. |
docs/installation/android.mdx |
Improves Android snippet metadata. |
docs/index.mdx |
Adds desktop support and correct usage. |
docs/getting-started.mdx |
Updates version and desktop links. |
docs/features/settings.mdx |
Rewrites settings documentation. |
docs/features/services.mdx |
Rewrites service documentation. |
docs/features/permissions.mdx |
Updates foreground/background permission guidance. |
docs/features/notification.mdx |
Documents notification images and byte icons. |
docs/features/listen-location.mdx |
Updates streaming/background examples. |
docs/features/get-location.mdx |
Documents current and cached locations. |
docs.json |
Adds desktop navigation links. |
.github/workflows/location-prepare.yaml |
Modernizes Flutter preparation jobs. |
.github/workflows/location-desktop.yaml |
Adds Linux and Windows CI builds. |
.github/workflows/deploy-web-demo.yaml |
Adds GitHub Pages deployment. |
Files not reviewed (1)
- packages/location/example/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
Comments suppressed due to low confidence (2)
packages/location/android/src/main/kotlin/com/lyokone/location/FlutterLocation.kt:713
- This sends a method-channel error and then throws
ActivityNotFoundException, allowing the exception to escape the handler after the result was already completed. A headlessrequestService()call should return theMISSING_ACTIVITYPlatformException only; remove the native throw.
fun requestService(requestServiceResult: Result) {
val activity = this.activity
if (activity == null) {
requestServiceResult.error("MISSING_ACTIVITY", "You should not requestService activation outside of an activity.", null)
throw ActivityNotFoundException()
packages/location/windows/location_plugin.cpp:75
- An unavailable WinRT speed is serialized as
0.0, which is also a valid stationary measurement.LocationData.speedis nullable specifically to represent unavailable data, so emit a nullEncodableValueinstead.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Stream listen no longer fails solely because Activity is null; start updates when permission is granted (SharedEngine / AA / CarPlay). Also return Flutter errors instead of throwing when permission/service UI needs an Activity.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 116 out of 119 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- packages/location/example/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
Comments suppressed due to low confidence (12)
packages/location/android/src/main/kotlin/com/lyokone/location/FlutterLocationService.kt:257
- On Android 11+ foreground and background location permissions must not be requested together; the system ignores a combined request. This array always includes
ACCESS_FINE_LOCATIONwithACCESS_BACKGROUND_LOCATION, so the defaultenableBackgroundMode()flow cannot obtain the background grant on API 30+ even when foreground access was already granted. Request background access separately (and follow the settings flow required by the OS version).
ActivityCompat.requestPermissions(
it,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
),
packages/location/linux/location_plugin.cc:211
- A second concurrent
getLocation()clears the first retained method call without responding to it. The first Dart Future then hangs permanently. Keep all pending one-shot calls and resolve them together, as the Android and Darwin implementations do.
packages/location/linux/location_plugin.cc:115 - After a one-shot result is delivered, the GeoClue client remains started when no stream is active. This leaves location acquisition running after
getLocation()completes, wasting power and continuing location access for the lifetime of the plugin. Stop the client after clearing the pending call whenstreamingis false.
packages/location/windows/location_plugin.cpp:150 PositionChangedcan invoke this delegate on a WinRT worker thread, but FlutterEventSink::Successmust be called on the platform thread. Sending directly from this callback can race channel teardown or fail in the engine. Marshal the event back to the registrar/platform task runner before touchingevent_sink_.
packages/location/windows/location_plugin.cpp:208- Unlike the
getLocationcoroutine, thisfire_and_forgetcoroutine has no exception handling.RequestAccessAsync()can throw anhresult_error(for example when the API is unavailable or blocked by policy), and an unhandled exception in a C++/WinRTfire_and_forgetcoroutine terminates the process instead of completing the Dart call with an error.
packages/location/windows/location_plugin.cpp:167 - The new public
getLastKnownLocation()call is not dispatched on Windows, so it reachesNotImplemented()and throws instead of returning cached data ornull. This contradicts the API documentation, which identifies web—not Windows—as the platform that always returnsnull. Add a Windows handler with the documented nullable result.
packages/location/linux/location_plugin.cc:201 - The new public
getLastKnownLocation()method has no Linux dispatch branch, so Linux callers receiveMissingPluginExceptionrather than the documented nullable result. Add a GeoClue-backed cached-location handler (or explicitly returnnullif no cache is available).
README.md:78 - Approximate location is a usable grant (
grantedLimited), but this quick-start returns early for that status. Users who choose approximate location therefore never reachgetLocation(), contrary to the permission documentation below.
if (await location.requestPermission() != PermissionStatus.granted) return;
packages/location/android/src/main/kotlin/com/lyokone/location/StreamHandlerImpl.kt:53
- Service binding is asynchronous, so Dart can subscribe before
initialize()supplieslocation. Returning here neither starts updates later nor reports an error; the subscription then stays silent indefinitely. Preserve the pending listener until the service connects, or fail it explicitly so callers can retry.
docs/installation/linux.mdx:11 - This is not out of the box on standard GeoClue installations. The implementation sets
DesktopIdfromg_get_prgname()and notes that GeoClue requires a matching installed.desktopfile, but the example/install CMake never installs one. Document that requirement (or bundle/install a matching desktop entry), otherwiseStartis rejected on normal systems.
Linux is working out of the box! 🎉
The plugin talks to [GeoClue2](https://gitlab.freedesktop.org/geoclue/geoclue)
over D-Bus, so a running `geoclue` service is required (it ships with most
desktop distributions). No extra dependency needs to be bundled with your app.
docs/features/notification.mdx:17
- The documented return type is incorrect: the API returns
Future<AndroidNotificationData?>, and the newly added example later accessesnotificationId/channelIdfrom that value. KeepingFuture<bool>here will mislead consumers and makes the page internally inconsistent.
String? imageName,
Uint8List? iconBytes,
Uint8List? imageBytes,
packages/location/android/src/main/kotlin/com/lyokone/location/MethodCallHandlerImpl.kt:90
- The public
changeSettingssignature permits explicit nulls, and the method channel always sends each key. These!!assertions therefore turn a valid call such aschangeSettings(interval: null)intoCHANGE_SETTINGS_ERROR. Either make these API parameters non-nullable or handle null by retaining/defaulting the corresponding setting.
val locationAccuracy = location.mapFlutterAccuracy[call.argument<Int>("accuracy")!!]
val updateIntervalMilliseconds = call.argument<Int>("interval")!!.toLong()
val fastestUpdateIntervalMilliseconds = updateIntervalMilliseconds / 2
val distanceFilter = call.argument<Double>("distanceFilter")!!.toFloat()
Queue method calls until the location service binds, complete pending getLocation/stream waiters when Activity is missing, request background location alone after foreground on API 29+, and fix web/platform_interface imports plus Permissions-API-missing timeout handling. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 116 out of 119 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- packages/location/example/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
Suppressed comments (8)
packages/location/windows/location_plugin.cpp:150
Geolocator.PositionChangedcallbacks are not guaranteed to run on Flutter's platform thread, butEventSink::Successmust be sent from that thread. Calling it directly here can race cancellation and invoke the binary messenger from the wrong thread. Capture the registrar/platform task runner and post the event back before touchingevent_sink_.
packages/location/linux/location_plugin.cc:211- A second concurrent
getLocationcall clears the first retainedFlMethodCallwithout responding to it, leaving the first DartFuturepending forever. Keep a collection of pending calls and resolve all of them on the next update, as the Darwin implementation does.
packages/location/windows/location_plugin.cpp:151 - Starting the Windows location stream subscribes immediately without first calling
Geolocator::RequestAccessAsync(). A caller that listens before invoking another permission-requesting method will therefore not get the documented permission prompt and may receive no updates. Request access asynchronously on listen, registerPositionChangedonly when allowed, and report denial through the event sink.
docs/features/permissions.mdx:38 - This states that background permission is always required, but the new
requireBackgroundPermission: falsepath intentionally supports Android foreground-service tracking withoutACCESS_BACKGROUND_LOCATION. Qualify this requirement as iOS/Always and Android only when that option remains enabled, so this page does not contradict the listening guide.
To keep receiving location updates while the app is in the background, the user
must grant "Allow all the time" (Always) access on top of the foreground grant.
packages/location/windows/location_plugin.cpp:237
- The Windows handler also omits the newly exposed
getLastKnownLocation()andisBackgroundPermissionGranted()methods, so both throwMissingPluginException. Add explicit handlers; a null cached location andfalsebackground permission are valid fallbacks.
packages/location/linux/location_plugin.cc:232 - The new public
getLastKnownLocation()andisBackgroundPermissionGranted()methods fall through tonotImplementedon Linux, causingMissingPluginExceptionrather than the documented nullable/boolean result. Handle these methods explicitly; returningnullandfalseis a conforming fallback until native cached-location support is added.
packages/location/darwin/location/Sources/location/LocationPlugin.swift:120 - When location services are disabled, this guard returns without invoking
result, sochangeSettings()leaves its DartFuturepending forever. Complete the call with aSERVICE_STATUS_DISABLEDerror (and also complete it ifselfhas already been released).
README.md:78 - This quick-start rejects
PermissionStatus.grantedLimited, even though that status permits location access and is documented as usable. Users granting approximate location will return early and never request a fix.
if (await location.requestPermission() != PermissionStatus.granted) return;
Without Info.plist location usage keys, Core Location never prompts, so requestPermission/getLocation/stream callers hung; return a FlutterError on all pending waiters instead of only logging.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 116 out of 119 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- packages/location/example/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
Suppressed comments (10)
packages/location/android/src/main/kotlin/com/lyokone/location/StreamHandlerImpl.kt:84
- Canceling the stream unconditionally stops the shared native request even when one or more concurrent
getLocation()calls are still waiting for that request's next fix. Those Futures then remain unresolved. Clear the stream sink first, but stop updates only when there are no pending one-shot results.
packages/location/windows/location_plugin.cpp:146 - Subscribing directly to
onLocationChangednever callsGeolocator::RequestAccessAsync; onlygetLocationand the permission methods do. This breaks the documented contract that the stream requests permission when needed: a first-use stream subscription can remain silent/denied. Request access asynchronously on the UI thread before registeringPositionChanged, and report denial through the event sink.
packages/location/windows/location_plugin.cpp:150 Geolocator.PositionChangedmay invoke this delegate off the Flutter platform thread, butEventSink::Successmust be sent on the platform thread. Calling it directly can race with cancellation/plugin destruction and violates the Windows embedder's channel threading requirements. Post the encoded event through the registrar's task runner (and re-check sink lifetime there).
docs/features/permissions.mdx:38- This statement contradicts the new
requireBackgroundPermission: falsebehavior documented elsewhere: an Android location foreground service can keep receiving updates withoutACCESS_BACKGROUND_LOCATION. State the platform distinction so users are not told the stricter grant is universally mandatory.
To keep receiving location updates while the app is in the background, the user
must grant "Allow all the time" (Always) access on top of the foreground grant.
packages/location/android/src/main/kotlin/com/lyokone/location/LocationPlugin.kt:164
- Queued method calls are flushed by
setLocation, beforesetLocationServiceis assigned. A call made whilebindServiceis pending—especially the newly documented standaloneenableBackgroundMode—therefore sees a null service and incorrectly returnsfalse(notification changes similarly returnnull). Assign the service first so the flush runs only after both dependencies are ready.
methodCallHandler?.setLocation(service.location)
methodCallHandler?.setLocationService(service)
packages/location/android/src/main/kotlin/com/lyokone/location/StreamHandlerImpl.kt:59
- The event channel is registered before the asynchronous service binding completes, but unlike method calls, an immediate stream subscription is rejected instead of queued. Apps that subscribe during startup can nondeterministically receive
SERVICE_NOT_READYand never start streaming. Retain the pending sink and start it fromsetLocationonce binding completes.
This issue also appears on line 80 of the same file.
packages/location_platform_interface/lib/location_platform_interface.dart:110
- The newly advertised Windows and Linux implementations do not handle the
getLastKnownLocationmethod, so this public call falls through toNotImplementedand throwsMissingPluginExceptionon both desktop platforms. Add handlers in both native plugins; returning a null success is valid when no cache API is available.
packages/location_platform_interface/lib/location_platform_interface.dart:145 - Neither the Windows nor Linux native method handler recognizes
isBackgroundPermissionGranted, despite both platforms now being registered bylocation. Calling this API there throwsMissingPluginExceptioninstead of returning a boolean. Implement the method on both desktop plugins (typicallyfalse, since they expose no separate background grant).
README.md:78 - This rejects
PermissionStatus.grantedLimited, even though approximate/reduced permission is sufficient forgetLocationand the package documentation explicitly treats it as usable. Users choosing approximate location will therefore exit the quick-start flow without receiving a location.
if (await location.requestPermission() != PermissionStatus.granted) return;
docs/features/notification.mdx:17
- The updated signature still documents
Future<bool>, but the actual API returnsFuture<AndroidNotificationData?>; the new example below also accessesnotificationIdandchannelIdfrom that value. Correct the signature so consumers do not code against an incompatible return type.
String? imageName,
Uint8List? iconBytes,
Uint8List? imageBytes,
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 120 out of 123 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- packages/location/example/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
Suppressed comments (19)
packages/location/windows/location_plugin.cpp:150
PositionChangedmay invoke this handler off the Flutter platform thread, butEventSink::Successmust be sent on that thread. The method-call path explicitly captures and awaitsui_thread; do the same for stream events to avoid thread-unsafe messenger access and intermittent crashes.
packages/location/windows/location_plugin.cpp:80- Converting through
time_ttruncates the WinRT timestamp to whole seconds before multiplying by 1000, so every WindowsLocationData.timeis rounded down to a second. Convert the WinRT duration directly to Unix milliseconds to preserve the timestamp precision promised by the API.
packages/location/linux/location_plugin.cc:211 - A second
getLocationcall clears the retained first call without sending any response, leaving the first DartFuturepending forever. Keep all pending one-shot calls (as the Android/Darwin implementations do) and resolve them together on the next fix.
packages/location/linux/location_plugin.cc:112 - After a one-shot request is fulfilled, the pending call is cleared but the GeoClue client is not stopped when no stream is active. A single
getLocation()therefore leaves system location tracking running until plugin disposal; stop the client here whenstreamingis false.
docs/features/permissions.mdx:38 - This is not true for the Android foreground-service mode documented elsewhere on this page set:
enableBackgroundMode(requireBackgroundPermission: false)intentionally works with foreground permission only. As written, this section tells those callers that “Allow all the time” is mandatory. Qualify the requirement by platform/mode and mention the foreground-service exemption.
## Background Permission
To keep receiving location updates while the app is in the background, the user
must grant "Allow all the time" (Always) access on top of the foreground grant.
.github/workflows/location-prepare.yaml:1
- This deletion removes the repository's only PR validation workflow; there are no remaining files under
.github/workflows, so formatting, analysis, Dart tests, Android lint/build, and iOS build checks will no longer run on pull requests. Keep or replace this workflow before merging the large platform rewrite.
packages/location/windows/location_plugin.cpp:217 MethodChannelLocationalways includes this key and the public parameter is nullable, so an explicit null still reaches the branch.std::get<int>on the nullEncodableValuethrowsbad_variant_access; the same problem exists fordistanceFilterandintervalbelow. Usestd::get_ifand only apply non-null values.
packages/location/windows/location_plugin.cpp:146- Subscribing to the Windows stream never calls
Geolocator::RequestAccessAsync. On a first-use install this can fail to register or remain silent instead of showing the permission prompt, contrary to theonLocationChangedcontract. Request access asynchronously and report denial through the event sink before attachingPositionChanged.
packages/location/windows/location_plugin.cpp:237 - The newly exposed
getLastKnownLocationandisBackgroundPermissionGrantedmethods are not handled anywhere above, so both fall through toNotImplementedon Windows. Calls through the public API throw instead of returning a cached location/nulland a permission boolean as documented.
packages/location/linux/location_plugin.cc:232 - Linux also falls through for the newly public
getLastKnownLocationandisBackgroundPermissionGrantedmethods. Since Linux is registered as a supported implementation, these calls throwMissingPluginExceptioninstead of returning the documented nullable location and boolean.
packages/location/android/src/main/kotlin/com/lyokone/location/LocationPlugin.kt:164 - Queued method calls are flushed as soon as
setLocationruns, butlocationServiceis assigned only on the next line. Calls queued duringbindServicethat need the service (enableBackgroundMode,isBackgroundModeEnabled, or notification updates) therefore execute withnulland return a false failure. Set the service first so flushing only happens after both dependencies are ready.
packages/location/android/src/main/kotlin/com/lyokone/location/MethodCallHandlerImpl.kt:132 - The public API permits these arguments to be explicitly
null, and the method channel forwards those nulls. The!!assertions then turn a valid call such aschangeSettings(interval: null)intoCHANGE_SETTINGS_ERROR; apply the documented defaults (or preserve current values) instead of force-unwrapping.
packages/location/linux/location_plugin.cc:226 changeSettingsreports success while ignoring every supplied setting; Linux remains hard-coded to exact accuracy and does not apply distance or interval filtering. This makes the cross-platform API silently lie to callers. Apply supported GeoClue/client-side settings, or return an explicit unsupported result rather than1.
packages/location/darwin/location/Sources/location/LocationPlugin.swift:413- Canceling the stream unconditionally stops Core Location even when one or more concurrent
getLocation()calls are still waiting for a fix. Those one-shot futures then never complete. Only stop updates here whenpendingLocationResultsis empty.
docs/installation/linux.mdx:11 - The implementation itself notes that GeoClue requires
DesktopIdto match an installed.desktopfile, but neither the example nor these installation steps install one. Standard GeoClue agents can therefore reject the client, so Linux is not reliably “out of the box.” Document the required desktop entry/GeoClue application setup (or bundle/install it).
Linux is working out of the box! 🎉
The plugin talks to [GeoClue2](https://gitlab.freedesktop.org/geoclue/geoclue)
over D-Bus, so a running `geoclue` service is required (it ships with most
desktop distributions). No extra dependency needs to be bundled with your app.
README.md:35
- The Android implementation now skips plugin registration unless the host engine is cached under the literal
SharedEnginekey, so this standard quick-start/cross-platform claim leads ordinary Android apps toMissingPluginException. Document the fork-specific engine creation/cache requirement prominently, including that the default Flutter engine is unsupported.
## ✨ Why Location?
- 🌍 **Truly cross-platform** — the same code runs on all six Flutter targets, no per-platform branching.
- ⚡ **One-line to a fix** — `await location.getLocation()` and you're done.
packages/location/android/src/main/kotlin/com/lyokone/location/StreamHandlerImpl.kt:57
- The event channel is registered before the asynchronous service bind completes, so an immediate
onLocationChanged.listencan hit this branch and fail withSERVICE_NOT_READY. Method calls are queued for the same race; stream subscriptions need equivalent deferred startup rather than an error based only on bind timing.
packages/location/darwin/location/Sources/location/LocationPlugin.swift:218 - This starts a one-shot request, but the delegate never implements
locationManager(_:didFailWithError:). If Core Location reports a terminal error after authorization, every pending result remains unresolved and streams receive no error. Add the delegate callback to complete pending results and notify the event sink while treating transientlocationUnknownappropriately.
packages/location_platform_interface/lib/src/types.dart:174 - Because
nullmeans “keep the current value” for every nullable field, callers cannot clear fields such asaccuracy,provider, orisMockonce set. AcopyWithfor nullable state needs an omitted-value sentinel (or wrappers) so “not supplied” and “replace with null” are distinguishable.
| locationServicesEnabled { [weak self] enabled in | ||
| guard let self, enabled else { return } |