Repository navigation
refactor: remove the get dependency - #46
ferrarafer wants to merge 4 commits into
Conversation
Reimplements all services on native Flutter APIs and drops `get`: - NavigationService now uses the native Navigator through an owned GlobalKey<NavigatorState>, with a StackedRouting model and a StackedNavigationConfig (replacing Get.routing / Get.config). - Transitions are built with PageRouteBuilder (buildTransitionRoute). - StackObserver feeds StackedRouting using native route types only. - DialogService uses showGeneralDialog; BottomSheetService uses showModalBottomSheet; GetPlatform.isAndroid -> defaultTargetPlatform. - SnackbarService uses a vendored GetSnackBar widget + controller (MIT, attributed) hosted on the navigator overlay; SnackbarConfig is preserved so the public API is unchanged. - get removed from pubspec.yaml. Adds a behavioural test suite (navigation, dialog, bottom sheet, snackbar, observer, transitions) as a safety net for the migration. BREAKING: StackedService.navigatorKey is now an owned key (was Get.key). config(enableLog/defaultGlobalState), popGesture and the bottom sheet enter/exit durations are kept for compatibility but are now no-ops. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqYch1wfAZ5J9fGBh38AKU
FilledStacks
left a comment
There was a problem hiding this comment.
Awesome @ferrarafer thanks for this addition.
Could you please update the example apps to use this, run the integration tests and see if it all passes please.
That should show us if the basics are still working.
Adds an integration_test suite that drives the example app end-to-end to verify the get-removal refactor's behaviour through the public API: - DialogService -> showGeneralDialog - SnackbarService -> vendored GetSnackBar on the navigator overlay - BottomSheetService -> showModalBottomSheet - NavigationService -> native Navigator + PageRouteBuilder transitions Each test dismisses its transient UI and settles before ending so the shared StackedService overlay/navigator tickers are disposed cleanly. Also adds the integration_test dev dependency + web driver entrypoint, and replaces deprecated MaterialStateProperty with WidgetStateProperty in setup_bottom_sheet_ui.dart so the example analyzes clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QX2hw8ST4QPV8DpockczFm
The example's Android scaffolding (Gradle 5.6.2, AGP 7, jcenter, legacy plugin loader) couldn't build under JDK 21, and iOS lacked the RunnerTests XCTest target that on-device integration tests require. Regenerated both platforms via `flutter create` and finished the migration by hand: Android: - Kotlin-DSL build files (settings/app/root build.gradle.kts), AGP 9.0.1, Gradle wrapper 9.1.0, mavenCentral. - Removed stale Groovy build files and the old com.example.example MainActivity package. - gradle.properties aligned to the current template (dropped obsolete enableR8/enableJetifier). - Manifests: dropped the AGP-incompatible package= attribute and added android:exported on the launcher activity. iOS: - Regenerated the Runner project with the RunnerTests target wired into project.pbxproj (flutter create won't inject it into an existing project), plus the modern UIScene embedding (SceneDelegate) and Podfile. App/bundle id is now com.example.stacked_services_example. Verified: migration_smoke_test passes on the Android emulator and the iOS simulator (DialogService, SnackbarService, BottomSheetService, NavigationService), and `flutter analyze` is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QX2hw8ST4QPV8DpockczFm
|
Validated against the example apps in Stacked-Org/stacked#1191. Both |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSHtjk6H5yhz977P7YXgPM
Eimen2018
left a comment
There was a problem hiding this comment.
Thanks Fernando, this is a big step forward. I checked out the branch, ran it, and compared each service against what Get actually did. The 43 tests pass, analysis is clean, and a fresh app created with the CLI builds against this branch with no changes. None of the other Stacked packages use Get. Snackbar queueing and cleanup look solid, and the route observer matches Get's.
Before merging
- Two must-fix items in
NavigationService(back()andnavigateToView) and one important one inDialogService(navigatorKeyis ignored). Details inline. - The rest of the inline notes are small behaviour differences. Either match the old behaviour or add them to the breaking changes list.
Release
- The commits are
refactor:and the note saysBREAKING:. With our semantic-release setup that publishes nothing. For a major release the squash commit needsfeat!:in the title or aBREAKING CHANGE:footer.
Breaking changes list, missing items
- The
.toGetextensions onTransition,SnackPositionandSnackStylewere removed. - Apps that imported
package:getwithout declaring it, or usedGetMaterialApp, will stop compiling.
Other
- Stacked-Org/stacked#1163 (custom snackbar not showing after an
await) is still present here, same asmain. The one-line fix in #47 applies cleanly on top of this branch. #47 and this PR conflict only on formatting indialog_service.dart. - The two vendored files credit Get in the header, but MIT asks for the licence text itself to be included. A short NOTICE file would cover it.
- Nice side effect:
completeDialogno longer closes an open snackbar instead of the dialog, which was the cause of Stacked-Org/stacked#1170.
| G.Get.back<T>(result: result, id: id); | ||
| return G.Get.key.currentState?.canPop() ?? false; | ||
| final navigator = _navigator(id); | ||
| navigator?.pop<T>(result); |
There was a problem hiding this comment.
Must fix. back() now pops even when this is the last screen. With Get it did nothing in that case. Calling back() on the first screen now removes it and leaves a blank screen.
| navigator?.pop<T>(result); | |
| if (navigator?.canPop() ?? false) navigator?.pop<T>(result); |
| transition: transitionStyle?.toGet ?? transition?.toGet, | ||
| ); | ||
| curve: curve ?? Curves.linear, | ||
| settings: RouteSettings(arguments: arguments), |
There was a problem hiding this comment.
Must fix. Two things changed here compared to Get:
preventDuplicatesis accepted but never used, so the same view can be pushed twice.- The route has no name. Get named it
/<WidgetType>. NowcurrentRouteis an empty string after this call.
Same in navigateWithTransition, replaceWithTransition and clearStackAndShowView. Suggest naming the route '/${view.runtimeType}' and returning null when it equals currentRoute and preventDuplicates is true.
| popGesture: popGesture, | ||
| transition: transitionStyle?.toGet ?? transition?.toGet, | ||
| ); | ||
| curve: curve ?? Curves.linear, |
There was a problem hiding this comment.
Small visual change: Get's default curve was Curves.easeOutQuad. This is now Curves.linear (also on lines 133 and 177), so transitions will feel a bit different.
| /// Returns the navigator state for the given nested navigation [id], or the | ||
| /// root navigator when [id] is null. | ||
| NavigatorState? _navigator([int? id]) => (id != null | ||
| ? StackedService.nestedNavigationKey(id) |
There was a problem hiding this comment.
With Get, an id that was never registered threw Route id not found. Here nestedNavigationKey(id) creates a new unused key, so the call quietly returns null. A typo in the id becomes hard to spot. An assert or a throw would keep the old behaviour.
| return Get.dialog<DialogResponse>( | ||
| PlatformDialog( | ||
| final key = navigatorKey ?? StackedService.navigatorKey; | ||
| return showGeneralDialog<DialogResponse>( |
There was a problem hiding this comment.
Important. showGeneralDialog uses the root navigator by default, so the navigatorKey parameter has no effect when the key belongs to a nested navigator. The dialog always opens on the root. With Get it was pushed onto the navigator of the key you passed.
Adding useRootNavigator: false here and in showCustomDialog (line 226) fixes it.
|
|
||
| return Get.bottomSheet<SheetResponse<T>>( | ||
| Material( | ||
| return showModalBottomSheet<SheetResponse<T>>( |
There was a problem hiding this comment.
Get's bottom sheet defaulted to a transparent background, so custom sheets drew only their own UI. showModalBottomSheet uses the theme's solid colour, so custom sheets now get a panel behind them.
Suggest adding backgroundColor: Colors.transparent, to this call.
|
|
||
| /// The routing state maintained by [routeObserver]. Native replacement for | ||
| /// `Get.routing`. | ||
| static final StackedRouting routing = StackedRouting(); |
There was a problem hiding this comment.
This state is now static for the life of the process and nothing resets it. In tests, isDialogOpen from one test is still true in the next. A small @visibleForTesting reset method would help.
| } | ||
|
|
||
| void _configureOverlay() { | ||
| _overlayState = StackedService.navigatorKey!.currentState!.overlay; |
There was a problem hiding this comment.
This crashes with a bare null check when the key is not attached to a navigator, for example with MaterialApp.router. main has the same problem, so not new. A clear error telling people to attach StackedService.navigatorKey would save them time.
| var item = _queue.removeAt(0); | ||
| try { | ||
| item.completer.complete(await item.job()); | ||
| } on Exception catch (e) { |
There was a problem hiding this comment.
This only catches Exception. A null check failure is an Error, so it slips through, and because _check() is not awaited the caller cannot catch it either. Copied as-is from Get, so not new. Catching Object and passing it to the completer would let callers handle it.
| @@ -1,3 +1,16 @@ | |||
| # Unreleased | |||
There was a problem hiding this comment.
semantic-release writes this file on every release, so a manual edit will conflict or be overwritten. Better to drop it and put the notes in the commit message.
Summary
Removes the
getdependency fromstacked_services, reimplementing every service on native Flutter APIs. The public API of the services is preserved.The migration was done in phases, each verified green against a behavioural test suite added up front as a safety net:
NavigationServicenow uses the nativeNavigatorthrough an ownedGlobalKey<NavigatorState>. NewStackedRoutingmodel +StackedNavigationConfigreplaceGet.routing/Get.config. Transitions are built withPageRouteBuilder(buildTransitionRoute).StackObserverfeedsStackedRoutingusing native route types only.Get.dialog/Get.generalDialog→showGeneralDialog;GetPlatform.isAndroid→defaultTargetPlatform.Get.bottomSheet→showModalBottomSheet.GetSnackBarwidget + controller vendored intolib/src/snackbar/vendored/(MIT, attributed), hosted on the navigator overlay.SnackbarConfigis unchanged.getremoved.Why
Drops a heavy, monolithic dependency in favour of native Flutter, improving WASM compatibility and aligning with Stacked's existing
auto_route-based direction. No third-party replacement is introduced.Tests
New behavioural suite (43 tests, all green): navigation, dialog, bottom sheet, snackbar, route observer, transitions, and
StackedService. The tests reference only the public surface, so they passed against thegetimplementation and continue to pass after removal.flutter analyzeis clean except 2 pre-existing@requiredinfos.StackedService.navigatorKeyis now an ownedGlobalKey<NavigatorState>instead ofGet.key.NavigationService.config'senableLoganddefaultGlobalStateare now no-ops.popGestureand the bottom sheetenterBottomSheetDuration/exitBottomSheetDurationare kept for compatibility but are currently no-ops.Suggest releasing as a major version.
🤖 Generated with Claude Code