You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Navigator 2.0:解决无效URL导致404页面历史循环问题

Flutter Navigator 2.0 404页面返回循环问题解决

问题现象

  • 用户输入无效URL后,该URL被存入浏览器历史,同时路由跳转到404页面;
  • 点击浏览器返回按钮时,路由回到无效URL,再次重定向到404页面,陷入循环;
  • 仅能通过右键点击返回按钮选择上上个页面跳出循环。

复现代码

import 'package:flutter/material.dart';

class MyConfiguration {
  const MyConfiguration.details()
      : isDetail = true,
        isUnknown = false;

  const MyConfiguration.unknown()
      : isDetail = false,
        isUnknown = true;

  const MyConfiguration.home()
      : isDetail = false,
        isUnknown = false;

  final bool isDetail;
  final bool isUnknown;
}

class MyRouteParser extends RouteInformationParser<MyConfiguration> {
  const MyRouteParser() : super();

  @override
  Future<MyConfiguration> parseRouteInformation(
      RouteInformation routeInformation) async {
    final location = routeInformation.location!;
    final uri = Uri.parse(location);

    if (uri.pathSegments.isEmpty) {
      return const MyConfiguration.home();
    }

    if (uri.pathSegments.length == 1 && uri.pathSegments.first == 'details') {
      return const MyConfiguration.details();
    }

    return const MyConfiguration.unknown();
  }

  @override
  RouteInformation? restoreRouteInformation(MyConfiguration configuration) {
    if (configuration.isDetail) {
      return const RouteInformation(location: '/details');
    }
    if (configuration.isUnknown) {
      return const RouteInformation(location: '/404'); // #1
    }
    return const RouteInformation(location: '/');
  }
}

class MyRouterDelegate extends RouterDelegate<MyConfiguration>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin {
  @override
  MyConfiguration currentConfiguration = const MyConfiguration.unknown();

  @override
  final navigatorKey = GlobalKey<NavigatorState>();

  @override
  Future<void> setNewRoutePath(MyConfiguration configuration) async {
    currentConfiguration = configuration;
  }

  @override
  Widget build(BuildContext context) {
    final homePage = MaterialPage(
        key: const ValueKey('home'),
        child: Scaffold(
          appBar: AppBar(title: const Text('Home')),
          body: TextButton(
              onPressed: () {
                currentConfiguration = const MyConfiguration.details();
                notifyListeners();
              },
              child: const Text('Show Details')),
        ));

    final detailPage = MaterialPage(
        key: const ValueKey('details'),
        child: Scaffold(
          appBar: AppBar(title: const Text('Details')),
        ));

    final unknownPage = MaterialPage(
        key: const ValueKey('unknown'),
        child: Scaffold(
          appBar: AppBar(title: const Text('Unknown')),
        ));

    return Navigator(
      key: navigatorKey,
      pages: [
        homePage,
        if (currentConfiguration.isDetail)
          detailPage
        else if (currentConfiguration.isUnknown)
          unknownPage,
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }
        currentConfiguration = const MyConfiguration.home();
        notifyListeners();
        return true;
      },
    );
  }
}

void main() {
  runApp(MaterialApp.router(
      routeInformationParser: const MyRouteParser(),
      routerDelegate: MyRouterDelegate()));
}

尝试方案及问题

修改restoreRouteInformation方法,当配置为unknown时返回null:

if (configuration.isUnknown) {
  return null;
}

但此操作会导致地址栏无法显示/404路径。

历史按钮在输入无效URL前正常工作,之后陷入循环

解决方案

问题核心是:解析无效URL得到unknown配置后,restoreRouteInformation返回/404会让浏览器将/404添加到历史栈,但原无效URL仍保留在栈中。点击返回时,浏览器回到无效URL,再次被解析为unknown,重定向到/404形成循环。

解决思路是在解析无效URL时,直接替换历史栈中的无效记录,而非添加新记录:

1. 更新MyConfiguration,保存原始无效路径

class MyConfiguration {
  const MyConfiguration.details()
      : isDetail = true,
        isUnknown = false,
        invalidPath = null;

  const MyConfiguration.unknown([this.invalidPath])
      : isDetail = false,
        isUnknown = true;

  const MyConfiguration.home()
      : isDetail = false,
        isUnknown = false,
        invalidPath = null;

  final bool isDetail;
  final bool isUnknown;
  final String? invalidPath; // 新增:存储原始无效路径
}

2. 修改RouteParser,传递无效路径

@override
Future<MyConfiguration> parseRouteInformation(
    RouteInformation routeInformation) async {
  final location = routeInformation.location!;
  final uri = Uri.parse(location);

  if (uri.pathSegments.isEmpty) {
    return const MyConfiguration.home();
  }

  if (uri.pathSegments.length == 1 && uri.pathSegments.first == 'details') {
    return const MyConfiguration.details();
  }

  // 若为/404,直接返回无invalidPath的unknown
  if (uri.path == '/404') {
    return const MyConfiguration.unknown();
  }

  // 其他无效路径,传递原始路径
  return MyConfiguration.unknown(location);
}

3. 重写RouterDelegate的setNewRoutePath方法处理重定向

需要先导入flutter/services.dart包:

import 'package:flutter/services.dart';

@override
Future<void> setNewRoutePath(MyConfiguration configuration) async {
  if (configuration.isUnknown && configuration.invalidPath != null) {
    // 替换历史栈中的无效URL为/404
    SystemNavigator.routeInformationUpdated(
      location: '/404',
      state: null,
    );
    // 更新当前配置为无invalidPath的unknown
    currentConfiguration = const MyConfiguration.unknown();
  } else {
    currentConfiguration = configuration;
  }
  notifyListeners();
}

4. 保留restoreRouteInformation原有逻辑

确保地址栏显示/404:

@override
RouteInformation? restoreRouteInformation(MyConfiguration configuration) {
  if (configuration.isDetail) {
    return const RouteInformation(location: '/details');
  }
  if (configuration.isUnknown) {
    return const RouteInformation(location: '/404');
  }
  return const RouteInformation(location: '/');
}

效果说明

  • 用户输入无效URL时,浏览器历史栈中的无效记录会被替换为/404,不会保留无效URL;
  • 点击返回按钮时,直接回到上一个有效页面(如Home或Details),不会陷入循环;
  • 地址栏始终显示/404,符合预期。

内容的提问来源于stack exchange,提问作者andreas1724

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 11:18:14