Migrating off the discontinued flutter_adaptive_scaffold - keeping go_router and your state
DEV Community

Migrating off the discontinued flutter_adaptive_scaffold - keeping go_router and your state

Google discontinued flutter_adaptive_scaffold in April 2025 - not because it was unpopular (it still pulls thousands of downloads a week), but to refocus on core framework work. The ask was for the community to rally around one sustainable fork instead of a dozen one-offs.

Most of the forks that appeared renamed the package and added cosmetic options - but the ones I found didn't address the two things you actually hit in a real app:

  • go_router integration. The original predated StatefulShellRoute, so wiring adaptive navigation to a router meant a pile of glue code (tracked upstream as flutter#129850).
  • State loss on resize. The original assigned a different body slot/key per breakpoint, so dragging a desktop window narrow - or a foldable unfolding - rebuilt your whole body subtree, wiping scroll position, form input and in-memory state.

adaptive_scaffold_router is a drop-in successor that fixes both.

Migrating is a one-line import change

The core API (AdaptiveScaffold, AdaptiveLayout, SlotLayout, Breakpoint, Breakpoints) is compatible with flutter_adaptive_scaffold 0.3.x. Swap the dependency and the import:

dependencies:
  adaptive_scaffold_router: ^0.3.1
  # was: flutter_adaptive_scaffold
import 'package:adaptive_scaffold_router/adaptive_scaffold_router.dart';

Existing AdaptiveScaffold(...) code keeps compiling.

The upgrade: wire it to go_router

Instead of hand-rolling glue, hand a StatefulNavigationShell to AdaptiveNavigationShell:

final router = GoRouter(
  initialLocation: '/inbox',
  routes: [
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) => AdaptiveNavigationShell(
        navigationShell: navigationShell,
        destinations: const [
          NavigationDestination(icon: Icon(Icons.inbox), label: 'Inbox'),
          NavigationDestination(icon: Icon(Icons.article), label: 'Articles'),
          NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
      branches: [
        StatefulShellBranch(routes: [GoRoute(path: '/inbox', builder: ...)]),
        StatefulShellBranch(routes: [GoRoute(path: '/articles', builder: ...)]),
        StatefulShellBranch(routes: [GoRoute(path: '/settings', builder: ...)]),
      ],
    ),
  ],
);

That's the whole adaptive shell. On a phone you get a bottom navigation bar; on a tablet, a navigation rail; on desktop, an extended rail. Each tab keeps its own navigation stack via go_router branches.

Why state survives a resize

Under the hood the shell feeds one body to AdaptiveScaffold (not a per-breakpoint body), so the underlying SlotLayout keeps a single stable key for every width. Changing the breakpoint swaps only the navigation chrome - the body subtree is never torn down.

Drag the window from desktop to phone width mid-scroll and the scroll position (and any half-filled form) is exactly where you left it. That's the bug the original couldn't fix without a key-per-breakpoint redesign.

New in 0.3.0: the full Material 3 progression

Material 3's adaptive navigation guidance is bottom bar → rail → drawer as the window widens. Set one flag and the largest breakpoints render a permanent NavigationDrawer instead of the extended rail:

AdaptiveNavigationShell(
  navigationShell: navigationShell,
  permanentDrawer: true,
  destinations: destinations,
)

The rail still shows at medium widths, and - because the drawer lives in the navigation slot, not the body - switching between rail and drawer preserves branch state too.

Also in the box

  • Cupertino / platform-adaptive bottom bar - bottomNavigationBuilder (or cupertino: true) swaps the small-breakpoint bar for a CupertinoTabBar on Apple platforms.
  • Badges - compose a Material Badge straight into a destination's icon.
  • Rail header / footer - leadingExtendedNavRail and trailingNavRail.
  • Two-pane body + secondaryBody for list/detail, foldable-aware.

160/160 pub points, BSD-3 licensed, actively maintained. Issues and PRs welcome on GitHub.

If you've got a flutter_adaptive_scaffold app gathering dust on a pinned version, this is the path forward - change the import, optionally add the shell, and you're on a maintained package again.

Comments

No comments yet. Start the discussion.