Skip to content

refactor: remove the get dependency - #46

Open
ferrarafer wants to merge 4 commits into
mainfrom
refactor/remove-get-dependency
Open

ferrarafer wants to merge 4 commits into
mainfrom
refactor/remove-get-dependency

Conversation

@ferrarafer

Copy link
Copy Markdown
Contributor

Summary

Removes the get dependency from stacked_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:

  • Navigation — NavigationService now uses the native Navigator through an owned GlobalKey<NavigatorState>. New StackedRouting model + StackedNavigationConfig replace Get.routing / Get.config. Transitions are built with PageRouteBuilder (buildTransitionRoute). StackObserver feeds StackedRouting using native route types only.
  • Dialog — Get.dialog/Get.generalDialog → showGeneralDialog; GetPlatform.isAndroid → defaultTargetPlatform.
  • Bottom sheet — Get.bottomSheet → showModalBottomSheet.
  • Snackbar — GetSnackBar widget + controller vendored into lib/src/snackbar/vendored/ (MIT, attributed), hosted on the navigator overlay. SnackbarConfig is unchanged.
  • pubspec — get removed.

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 the get implementation and continue to pass after removal.

flutter analyze is clean except 2 pre-existing @required infos.

⚠️ Breaking changes

  • StackedService.navigatorKey is now an owned GlobalKey<NavigatorState> instead of Get.key.
  • NavigationService.config's enableLog and defaultGlobalState are now no-ops.
  • popGesture and the bottom sheet enterBottomSheetDuration / exitBottomSheetDuration are kept for compatibility but are currently no-ops.

Suggest releasing as a major version.

🤖 Generated with Claude Code

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
@ferrarafer
ferrarafer requested a review from FilledStacks June 19, 2026 15:24

@FilledStacks FilledStacks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

ferrarafer and others added 2 commits June 22, 2026 10:11
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
@ferrarafer

Copy link
Copy Markdown
Contributor Author

Validated against the example apps in Stacked-Org/stacked#1191. Both navigator_example and router_example were updated to consume this branch's get-free stacked_services (and had their leftover get direct dependency removed); their integration test suites pass green on Android — covering navigation/back, stream sources, the form (date picker, dropdown, validation), and nested bottom-nav.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSHtjk6H5yhz977P7YXgPM

@Eimen2018 Eimen2018 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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() and navigateToView) and one important one in DialogService (navigatorKey is 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 says BREAKING:. With our semantic-release setup that publishes nothing. For a major release the squash commit needs feat!: in the title or a BREAKING CHANGE: footer.

Breaking changes list, missing items

  • The .toGet extensions on Transition, SnackPosition and SnackStyle were removed.
  • Apps that imported package:get without declaring it, or used GetMaterialApp, will stop compiling.

Other

  • Stacked-Org/stacked#1163 (custom snackbar not showing after an await) is still present here, same as main. The one-line fix in #47 applies cleanly on top of this branch. #47 and this PR conflict only on formatting in dialog_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: completeDialog no 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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Suggested change
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),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Must fix. Two things changed here compared to Get:

  1. preventDuplicates is accepted but never used, so the same view can be pushed twice.
  2. The route has no name. Get named it /<WidgetType>. Now currentRoute is 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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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>(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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>>(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread CHANGELOG.md
@@ -1,3 +1,16 @@
# Unreleased

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

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.

3 participants