Feature SDKs
How to scroll to a widget from anywhere in a Flutter app
Free Reference for autoscroll_sdk: every variable, option and package it generates. Command: archipelago generate autoscroll_sdk
Generate the Autoscroll SDK, register a GlobalKey during build, scroll to it on demand, and learn why unregistered ids do nothing.
What you'll learn
- Generating the Autoscroll SDK
- Registering a widget location's
GlobalKeyduring build - Scrolling to a registered location from anywhere in the app
- Understanding the no-op behavior for unregistered or unmounted ids
- Why this SDK does not support scrolling deep into virtualized lists
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
Step 1: Generate the Autoscroll SDK
The Autoscroll SDK is a Free tier brick.
archipelago generate autoscroll_sdkThere are no generation variables. The SDK is string-keyed at runtime, so there is no fixed list of screen names to maintain.
Step 2: Understand the Generated Structure
The Autoscroll SDK uses the API/Impl split pattern:
features/
└── autoscroll_sdk/
├── autoscroll_sdk_api/ # AutoscrollSdk contract — no models, no plugin
└── autoscroll_sdk_impl/ # In-memory Map<String, GlobalKey> registry
└── lib/src/
├── autoscroll_sdk_impl.dart
└── di/ # AutoscrollSdkImplPackageModuleFeatures that register or scroll to a location depend only on autoscroll_sdk_api.
The post-generation hook adds both packages to the workspace and the impl to the app pubspec.yaml. It registers AutoscrollSdkImplPackageModule in lib/di/injector.dart. The SDK is a lazy singleton that holds an in-memory map.
Step 3: Register a Widget Location
Call getOrCreateKey(id) once per build, on the widget you want to be able to scroll back to. It is safe to call on every build. Repeated calls with the same id return the same key instance:
// A transaction list item, keyed by its own id:
final autoscroll = getIt<AutoscrollSdk>();
ListTile(
key: autoscroll.getOrCreateKey('transaction-$transactionId'),
title: Text(transaction.title),
);Step 4: Scroll to a Registered Location
From anywhere else in the app, call scrollTo. A deep-link handler or a notification tap handler is a typical caller. Call it once the owning screen is built:
final autoscroll = getIt<AutoscrollSdk>();
await autoscroll.scrollTo('transaction-42');scrollTo accepts optional duration, curve, alignment, and alignmentPolicy parameters, all forwarded to Scrollable.ensureVisible. The defaults animate a short scroll that centers the target in the viewport:
await autoscroll.scrollTo(
'transaction-42',
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
alignment: 0, // pin to the leading edge instead of centering
);Step 5: Handle the No-op Case
scrollTo never throws. It returns false when:
idwas never registered viagetOrCreateKey, oridis registered but its widget is not currently mounted (e.g. the owning screen has not been built yet, or has already been disposed).
final scrolled = await autoscroll.scrollTo('transaction-42');
if (!scrolled) {
// Fall back to a default destination, or do nothing — a bad deep link
// should never crash the app.
await context.router.push(const TransactionListRoute());
}Step 6: Release Stale Registrations (Optional)
If a screen that owns a registered location is being permanently disposed and its id must not be reused, call release:
@override
void dispose() {
getIt<AutoscrollSdk>().release('transaction-42');
super.dispose();
}Call reset() to clear every registration at once. This helps in tests and after a sign-out.
Understanding the Virtualized-List Limitation
scrollTo resolves the target through its GlobalKey's current BuildContext, which must already exist. This works reliably for:
- Content currently on screen
- Content within a lazy list's cache extent
- Any non-lazy scrollable, such as a
Columnwrapped in aSingleChildScrollView
It does not support scrolling to an item far outside the built range of a virtualized list. There is no height cache for that case, which keeps the SDK simple. Reserve registered ids for content that is realistically already laid out, such as sections of a single screen. For guaranteed reachability deep into a long ListView.builder, use a dedicated package such as scrollable_positioned_list instead.
Common Customizations
| Goal | What to do |
|---|---|
| Scroll instantly, no animation | Pass duration: Duration.zero |
| Pin the target to the top of the viewport | Pass alignment: 0 |
| Pin the target to the bottom of the viewport | Pass alignment: 1 |
| Clear a stale registration on screen disposal | Call release(id) in dispose() |
| Reset all registrations (e.g. sign-out) | Call reset() |
Next Steps
- Wire
scrollTointo your deep-link handler alongside route navigation - Configure push notifications to land users on a specific transaction or section via a registered id