Skip to content

[go_router] material_ui MaterialApp routes lose platform transitions #191132

Closed
flutter/packages
#12504

Description

@AlexDochioiu

What package does this bug report belong to?

go_router

What target platforms are you seeing this bug on?

iOS and macOS are confirmed. The fallback to NoTransitionPage is platform-independent.

Have you already upgraded your packages?

Yes.

Dependency versions

Relevant pubspec.lock entries
cupertino_ui:
  dependency: transitive
  source: hosted
  version: "1.0.0"
go_router:
  dependency: "direct main"
  source: hosted
  version: "17.5.0"
material_ui:
  dependency: "direct main"
  source: hosted
  version: "1.0.0"

Steps to reproduce

  1. Run flutter create --empty bug with Flutter 3.47.0.
  2. Add go_router: ^17.5.0 and material_ui: ^1.0.0.
  3. Replace lib/main.dart with the code sample below.
  4. Run the app and tap Open details.
  5. Tap Back.

The sample forces TargetPlatform.iOS, so the expected Cupertino slide can be checked on any host. Removing that override reproduces the same missing slide on an iOS or macOS target.

Expected results

Routes declared with GoRoute.builder under material_ui.MaterialApp.router should receive the same platform-adaptive default page as routes under the legacy SDK MaterialApp.router. In this sample, pushing and popping Details should use the Cupertino horizontal slide.

Actual results

The route changes instantly with no page transition.

go_router imports MaterialApp from package:flutter/material.dart and checks it with findAncestorWidgetOfExactType<MaterialApp>(). A package:material_ui/material_ui.dart MaterialApp is a different type, so the check returns false. GoRouter then classifies the app as a WidgetsApp and wraps every route using builder in NoTransitionPage:

A widget test against the sample confirms that ModalRoute.of(detailsContext).settings is a NoTransitionPage<void>.

The workaround is to replace every GoRoute.builder and ShellRoute.builder with a pageBuilder that explicitly returns material_ui.MaterialPage. This restores the configured platform transition, but requires every route to opt out of GoRouter's normal page selection.

I found the general decoupling tracker (#101479) and the proposal for a configurable default page builder (#113282), but neither reports this standalone MaterialApp detection regression.

Code sample

Code sample
import 'package:go_router/go_router.dart';
import 'package:material_ui/material_ui.dart';

void main() => runApp(const MainApp());

final router = GoRouter(
  debugLogDiagnostics: true,
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => Scaffold(
        appBar: AppBar(title: const Text('Home')),
        body: Center(
          child: ElevatedButton(
            onPressed: () => context.push('/details'),
            child: const Text('Open details'),
          ),
        ),
      ),
    ),
    GoRoute(
      path: '/details',
      builder: (context, state) => Scaffold(
        appBar: AppBar(title: const Text('Details')),
        body: Center(
          child: ElevatedButton(
            onPressed: context.pop,
            child: const Text('Back'),
          ),
        ),
      ),
    ),
  ],
);

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      theme: ThemeData(platform: TargetPlatform.iOS),
      routerConfig: router,
    );
  }
}

Screenshots or Videos

Not included; the route changes instantly rather than animating.

Logs

There is no exception. The minimal sample analyzes successfully, and this test demonstrates the selected page type:

testWidgets('builder route falls back to NoTransitionPage', (tester) async {
  await tester.pumpWidget(const MainApp());
  await tester.pumpAndSettle();

  await tester.tap(find.text('Open details'));
  await tester.pump();

  final route = ModalRoute.of(tester.element(find.text('Details')));
  expect(route?.settings, isA<NoTransitionPage<void>>());
});
00:00 +0: builder route falls back to NoTransitionPage
00:00 +1: All tests passed!

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.47.0, on macOS 26.6.1 25G76 darwin-arm64, locale en-US)
    • Flutter version 3.47.0 on channel stable
    • Framework revision 4cf2416426 (2026-08-11)
    • Engine revision 5f77625673
    • Dart version 3.13.0
    • DevTools version 2.60.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-record-use, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0)
    • Platform android-36, build-tools 36.0.0
    • Java version OpenJDK Runtime Environment (build 21.0.9)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.6)
    • Build 17F113
    • CocoaPods version 1.17.0

[✓] Chrome - develop for the web
    • Chrome 151.0.7922.138

[✓] Connected devices
    • iOS physical devices (identifiers redacted)
    • macOS 26.6.1
    • Chrome

[✓] Network resources

• No issues found!

Activity

  1. KCO-SK commented on Aug 14, 2026

    @KCO-SK

    Confirming this on Flutter 3.47.0 / material_ui 1.0.0 / go_router 17.5.0, and I'd like to add a consequence that I think makes this more than a cosmetic regression: the Duration.zero fallback silently disables every Hero animation in the app.

    That is how it surfaced for us. We migrated to material_ui and the next day noticed that a contact avatar no longer flew between the list and the detail page on web. Nothing about the symptom points at routing — it reads as a broken Hero tag, a mismatched tag string, or a Navigator problem. It took a walk through builder.dart to find that the page had become a NoTransitionPage. Anyone with shared-element transitions will lose them here, and I suspect most will not connect it to isMaterialApp.

    Minimal check, same shape as your NoTransitionPage test — a hero flight lives in the overlay, so more than one Hero exists mid-transition:

    Widget hero(double size) => Hero(tag: 'pic', child: SizedBox.square(dimension: size));

    // route '/detail' declared with builder: -> find.byType(Hero) has 1 match (no flight)
    // route '/detail' declared with pageBuilder: -> find.byType(Hero) has 2 matches (flight)
    router.go('/detail');
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(tester.widgetList(find.byType(Hero)).length > 1, isTrue);

    Two more things worth folding into this issue:

    1. The pageBuilder workaround does not fully restore hero behaviour. isMaterialApp is consulted a second time, for the HeroController itself (builder.dart#L182-L191), so the app gets a bare HeroController() instead of MaterialApp.createMaterialHeroController() and heroes travel in a straight line rather than along the MaterialRectArcTween arc. This one cannot be worked around from outside: RouteBuilder installs its own HeroControllerScope directly above the Navigator, so an outer scope never wins. The only lever left is per-widget Hero(createRectTween:), which is not reachable for widgets that build their own hero internally — FloatingActionButton, for one.
    2. errorBuilder falls back to the unstyled ErrorScreen instead of MaterialErrorScreen through the same check.

    The part that makes this expensive is how quiet it is: no analyzer diagnostic, no assertion, no exception — just log('Using WidgetsApp configuration'), which is invisible unless debugLogDiagnostics: true happens to be set. Two first-party packages disagree about a type and the app degrades in silence.

  2. added
    c: regressionIt was better in the past than it is now
    packageflutter/packages repository. See also p: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    fyi-designFor the attention of Design Languages team
    on Aug 15, 2026
  3. mbcorona commented on Aug 15, 2026

    @mbcorona
    Member

    Thanks for the detailed report, routing to the framework team for evaluation.

  4. added
    P2Important issues not at the top of the work list
    on Aug 18, 2026
  5. dkwingsmt commented on Aug 19, 2026

    @dkwingsmt
    Contributor
  6. chunhtai commented on Aug 19, 2026

    @chunhtai
    Contributor

    this pr should address this flutter/packages#12504

  7. github-actions commented on Sep 2, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  8. locked as resolved and limited conversation to collaborators on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: regressionIt was better in the past than it is nowfound in release: 3.47Found to occur in 3.47has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packagepackageflutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions