Skip to content

Drop home_widget + geolocator for native; unpin Flutter to 3.44.0#18

Merged
timbortnik merged 1 commit into
mainfrom
strip-home-widget
May 31, 2026
Merged

Drop home_widget + geolocator for native; unpin Flutter to 3.44.0#18
timbortnik merged 1 commit into
mainfrom
strip-home-widget

Conversation

@timbortnik
Copy link
Copy Markdown
Owner

@timbortnik timbortnik commented May 31, 2026

Summary

Removes the last two Flutter plugins with native code — home_widget and
geolocator — replacing both with native implementations over the existing
method channel. With zero KGP-applying plugins left, AGP-9 built-in Kotlin
(android.builtInKotlin=true) is finally compatible with current Flutter, so
this also unpins Flutter 3.41.7 → 3.44.0.

Why: AGP-9 built-in Kotlin rejects the legacy Kotlin Gradle Plugin (KGP)
globally — a single KGP-applying plugin breaks the build on Flutter 3.44.0.
The project had pinned Flutter at 3.41.7 specifically to keep built-in Kotlin.
Going plugin-free removes the conflict at its root.

Changes

home_widgetWidgetStore (native key-value)

  • New lib/services/widget_store.dart: method-channel KV bridge over
    org.bortnik.meteogram/svg to the HomeWidgetPreferences file.
  • MainActivity ports home_widget's exact SharedPreferences codec — the
    home_widget.double.<key> boolean sidecar + doubleToRawLongBits for
    doubles — so existing installs keep their saved data.
  • MeteogramWidgetProvider now extends AppWidgetProvider (opens prefs in
    onUpdate); removed the dead HomeWidgetBackgroundReceiver/Service from
    the manifest.
  • Drops path_provider / jni / jni_flutter from the tree.

geolocator → native LocationProvider

  • New LocationProvider.kt (android.location.LocationManager) + Dart
    LocationBridge; runtime-permission flow handled in MainActivity
    (onRequestPermissionsResult).
  • No new dependencies, no Google Play Services. GPS fixes are
    foreground-only (the widget's background refresh reuses cached coords).
    Drops uuid.

Unpin Flutter

  • CI flutter-version 3.41.73.44.0 (test / release / codeql workflows).
  • CLAUDE.md, docs/ai/*, and gradle.properties comments updated to the new
    state, including the "keep the plugin set KGP-free" constraint.

Net result: GeneratedPluginRegistrant is now empty (no native plugins).

Verification (Flutter 3.44.0 / Dart 3.12.0)

  • make analyze clean; 115 Dart tests + Kotlin tests pass.
  • Debug + release APKs build (release 17.9 MB).
  • Emulator (Pixel 7 Pro, API 35): permission grant → GPS fix → weather
    renders ("Mountain View · GPS"); deny → Berlin fallback renders
    ("Berlin · Manual"). 0 crashes / SecurityExceptions.
  • Android Lint NewApi clean for the new native code at minSdk 24.

Notes & follow-ups

  • Builds emit one benign warning — Applying the Kotlin Android Plugin (KGP) was unsuccessful… — Flutter 3.44.0's caught force-apply on :app;
    the build succeeds ([AGP 9] Remove Support for KGP flutter/flutter#184837).
  • After switching local Flutter SDK versions, run flutter clean (stale
    Dart-kernel caches otherwise cause spurious dot-shorthands framework errors).
  • ⚠️ Do not add a plugin that applies KGP — it re-breaks built-in Kotlin.
    Use a native method-channel impl, or fall back to legacy KGP
    (builtInKotlin=false + re-add kotlin-android).
  • Pre-existing, out of scope (flagged by Lint): res/values/styles.xml's
    WidgetTheme uses Theme.DeviceDefault.DayNight (API 29) at minSdk 24 —
    worth a separate fix (move to values-v29/ with an API-24 fallback).
  • Not exercised on hardware: the pre-API-28/30 location branches (older
    devices). The AbstractMethodError risk is eliminated in code (all four
    LocationListener methods are overridden).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Updated Flutter SDK version from 3.41.7 to 3.44.0 across build workflows.
    • Removed third-party dependencies for location and widget functionality.
  • Refactor

    • Migrated widget persistence to native implementation for improved reliability.
    • Replaced location service with native Android implementation.
  • Documentation

    • Updated architecture and widget documentation to reflect new native-based approach.

The project pinned Flutter at 3.41.7 to keep AGP-9 built-in Kotlin
(android.builtInKotlin=true), which rejects the legacy Kotlin Gradle
Plugin (KGP) globally — so any plugin applying KGP broke the build on
Flutter 3.44.0. Removing the last two native plugins eliminates KGP
entirely, so built-in Kotlin and Flutter 3.44.0 now coexist.

home_widget -> WidgetStore:
- New lib/services/widget_store.dart: method-channel KV bridge over
  org.bortnik.meteogram/svg to the HomeWidgetPreferences file.
- MainActivity ports home_widget's exact codec (home_widget.double.<key>
  sidecar + doubleToRawLongBits) so existing installs keep saved data.
- MeteogramWidgetProvider extends AppWidgetProvider (opens prefs in onUpdate).
- Removed the dead HomeWidget background receiver/service from the manifest.

geolocator -> native:
- New LocationProvider.kt (android.location.LocationManager) + Dart
  LocationBridge; runtime-permission flow in MainActivity. No new deps,
  no Google Play Services. GPS fixes are foreground-only.

Result: GeneratedPluginRegistrant is empty (zero native plugins); also
dropped path_provider/jni/jni_flutter and uuid from the tree.

Unpin: CI flutter-version 3.41.7 -> 3.44.0; CLAUDE.md / docs / gradle
comments updated.

Verified on 3.44.0: analyze clean, 115 Dart + Kotlin tests pass, debug +
release APKs build, emulator run (grant -> GPS, deny -> Berlin fallback),
Android Lint NewApi clean for the new native code (minSdk 24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates widget persistence and location services from the Flutter home_widget package to native Android method-channel implementations. The toolchain is updated to Flutter 3.44.0; new native LocationProvider Kotlin code handles location queries; Dart bridges (WidgetStore, LocationBridge) wrap method-channel calls; and all services are updated to use the new persistence layer.

Changes

Native Method Channel Migration

Layer / File(s) Summary
Flutter toolchain and build configuration
.github/workflows/codeql-default.yml, .github/workflows/release.yml, .github/workflows/test.yml, android/gradle.properties
Flutter version pins updated from 3.41.7 to 3.44.0 across all CI workflows; AGP-9 built-in Kotlin compatibility comments updated to reference new Flutter version.
Android native location provider and permission handling
android/app/src/main/kotlin/org/bortnik/meteogram/LocationProvider.kt
New LocationProvider singleton implements location service detection, permission status mapping, last-known position retrieval (with provider priority), and one-shot current position fetching with configurable timeout and cleanup.
Method channel infrastructure and widget data persistence
android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt, android/app/src/main/AndroidManifest.xml
MainActivity expands method-channel handlers for widget data read/write (with Double encoding), widget refresh triggering, and location permission/position queries; removes HomeWidget background receiver/service from manifest.
Dart LocationBridge for native location APIs
lib/services/location_bridge.dart
New LocationBridge class provides static async APIs (isLocationServiceEnabled, checkPermission, requestPermission, getCurrentPosition, getLastKnownPosition, openLocationSettings) that wrap native calls, map permission status strings to enum, and handle PlatformException fallbacks.
Dart WidgetStore key-value bridge
lib/services/widget_store.dart
New WidgetStore utility bridges Dart to native widget storage over org.bortnik.meteogram/svg channel; provides getWidgetData, saveWidgetData (with null-removal semantics), and updateWidget.
Widget provider base class migration
android/app/src/main/kotlin/org/bortnik/meteogram/MeteogramWidgetProvider.kt
MeteogramWidgetProvider changes from HomeWidgetProvider to Android's standard AppWidgetProvider; onUpdate signature updated to standard form; SharedPreferences retrieved directly from context.
LocationService migration to LocationBridge and WidgetStore
lib/services/location_service.dart
LocationService now uses LocationBridge for permission/service checks and position retrieval; location persistence (latitude/longitude/city/source, GPS-mode flag, recent cities) switched from HomeWidget to WidgetStore.
Service layer migration to WidgetStore
lib/services/theme_service.dart, lib/services/material_you_service.dart, lib/services/native_svg_service.dart, lib/services/widget_service.dart, lib/screens/home_screen.dart, lib/main.dart
ThemeService, MaterialYouService, NativeSvgService, and WidgetService updated to use WidgetStore.getWidgetData/saveWidgetData instead of HomeWidget APIs; WidgetService.initialize() call removed from app startup.
Tests and documentation updates
test/home_screen_test.dart, test/location_service_test.dart, test/theme_service_test.dart, CLAUDE.md, docs/ai/architecture.md, docs/ai/widget.md, pubspec.yaml
Test mocks updated to use org.bortnik.meteogram/svg channel for widget-store and location-bridge calls; home_widget and geolocator mocks removed; project documentation updated to reflect native architecture; dependencies (home_widget, geolocator_android, geolocator_platform_interface) removed from pubspec.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • timbortnik/widget#15: Modifies theme persistence in ThemeService and widget provider wiring, which directly overlap with this PR's WidgetStore migration and MeteogramWidgetProvider base class change.
  • timbortnik/widget#17: Also extends MainActivity.kt Flutter method-channel handling with native APIs (openUrl, reverseGeocode), creating shared infrastructure with this PR's location and widget persistence handlers.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: removing two Flutter plugins (home_widget and geolocator) and replacing them with native implementations, while upgrading Flutter to 3.44.0.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch strip-home-widget

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt (1)

237-244: 💤 Low value

Minor: runOnUiThread is redundant here.

LocationProvider.getCurrentPosition already invokes its callback on the main thread via Handler(Looper.getMainLooper()). The runOnUiThread call is harmless (it will detect it's already on the UI thread and execute directly), but it's unnecessary indirection.

Not a blocking issue—leaving it won't cause problems, but removing it simplifies the code.

♻️ Optional simplification
                "getCurrentPosition" -> {
                    val timeoutMs = (call.argument<Int>("timeoutMs") ?: 15000).toLong()
                    LocationProvider.getCurrentPosition(this, timeoutMs) { coords ->
-                        runOnUiThread {
-                            result.success(coords?.let { mapOf("latitude" to it[0], "longitude" to it[1]) })
-                        }
+                        result.success(coords?.let { mapOf("latitude" to it[0], "longitude" to it[1]) })
                    }
                }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt` around
lines 237 - 244, The callback passed to LocationProvider.getCurrentPosition is
already delivered on the main thread, so remove the redundant runOnUiThread
wrapper; inside the "getCurrentPosition" branch use the existing callback lambda
to call result.success directly (preserve the timeoutMs computation and the
null-safe mapping of coords to mapOf("latitude" to it[0], "longitude" to it[1])
so behavior is unchanged). Update the lambda body in MainActivity's
"getCurrentPosition" case to call result.success(...) without runOnUiThread.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt`:
- Around line 237-244: The callback passed to
LocationProvider.getCurrentPosition is already delivered on the main thread, so
remove the redundant runOnUiThread wrapper; inside the "getCurrentPosition"
branch use the existing callback lambda to call result.success directly
(preserve the timeoutMs computation and the null-safe mapping of coords to
mapOf("latitude" to it[0], "longitude" to it[1]) so behavior is unchanged).
Update the lambda body in MainActivity's "getCurrentPosition" case to call
result.success(...) without runOnUiThread.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eea0e27f-e94e-4559-8cf5-a5b6b0778b21

📥 Commits

Reviewing files that changed from the base of the PR and between e8b881c and 2f2007f.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • .github/workflows/codeql-default.yml
  • .github/workflows/release.yml
  • .github/workflows/test.yml
  • CLAUDE.md
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/kotlin/org/bortnik/meteogram/LocationProvider.kt
  • android/app/src/main/kotlin/org/bortnik/meteogram/MainActivity.kt
  • android/app/src/main/kotlin/org/bortnik/meteogram/MeteogramWidgetProvider.kt
  • android/gradle.properties
  • docs/ai/architecture.md
  • docs/ai/widget.md
  • lib/main.dart
  • lib/screens/home_screen.dart
  • lib/services/location_bridge.dart
  • lib/services/location_service.dart
  • lib/services/material_you_service.dart
  • lib/services/native_svg_service.dart
  • lib/services/theme_service.dart
  • lib/services/widget_service.dart
  • lib/services/widget_store.dart
  • pubspec.yaml
  • test/home_screen_test.dart
  • test/location_service_test.dart
  • test/theme_service_test.dart
💤 Files with no reviewable changes (2)
  • lib/main.dart
  • android/app/src/main/AndroidManifest.xml

@timbortnik timbortnik merged commit 997c9df into main May 31, 2026
5 checks passed
timbortnik added a commit that referenced this pull request May 31, 2026
LocationProvider.getCurrentPosition already invokes its callback on the
main thread — requestLocationUpdates is registered with the main Looper,
the timeout uses a main-Looper Handler, and the early-return paths run
synchronously on the calling (main) thread. Wrapping result.success in
runOnUiThread was therefore unnecessary indirection.

Per CodeRabbit review on #18.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant