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

Flutter注册成功跳转LoginScreen报错:找不到路由生成器

问题描述

在Flutter项目中,用户完成注册后弹出确认弹窗,点击确认按钮需跳转至LoginScreen页面。由于首次登录后需正常启动登录流程,必须为LoginScreen传入LocationService,但跳转时触发如下错误:

======== Exception caught by gesture 
===============================================================
The following assertion was thrown while handling a gesture:
Could not find a generator for route RouteSettings("/loginScreen", null) in the _WidgetsAppState.

Make sure your root app widget has provided a way to generate 
this route.
Generators for routes are searched for in the following order:
 1. For the "/" route, the "home" property, if non-null, is used.
 2. Otherwise, the "routes" table is used, if it has an entry for the route.
 3. Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
 4. Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.
When the exception was thrown, this was the stack: 
#0      _WidgetsAppState._onUnknownRoute.<anonymous closure> (package:flutter/src/widgets/app.dart:1438:9)
#1      _WidgetsAppState._onUnknownRoute (package:flutter/src/widgets/app.dart:1453:6)
#2      NavigatorState._routeNamed (package:flutter/src/widgets/navigator.dart:4173:37)
#3      NavigatorState.pushNamed (package:flutter/src/widgets/navigator.dart:4217:21)
#4      Navigator.pushNamed (package:flutter/src/widgets/navigator.dart:1710:34)
#5      _RegisterScreenState._showMyDialog.<anonymous closure>.<anonymous closure> (package:sdx_patient_portal/presentation/auth/register_screen.dart:51:31)
#6      _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1154:21)
#7      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:275:24)
#8      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:654:11)
#9      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:311:5)
#10     BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:244:7)
#11     PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:630:9)
#12     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:98:12)
#13     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:143:9)
#14     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:625:13)
#15     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:141:18)
#16     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:127:7)
#17     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:465:19)
#18     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:445:22)
...
#25     PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:382:7)
#26     _dispatchPointerDataPacket (dart:ui/hooks.dart:91:31)
Handler: "onTap"
Recognizer: TapGestureRecognizer#61a5f
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(292.3, 416.0)
finalLocalPosition: Offset(52.6, 16.3)
button: 1
sent tap down
=======================

已在MyApp的routes中注册了/loginScreen路由,代码如下:

class MyApp extends StatefulWidget {
  static LocationService? locationService = LocationService();

  const MyApp({
    Key? key,
    required widget.homepage,
    required widget.isLoggedIn,
    required widget.isLightTheme,
  }) : super(key: key);
  final bool? isLoggedIn;
  final String homepage;
  final bool isLightTheme;

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  LocationService? locationService;
  @override
  void initState() {
    super.initState();
    MyApp.locationService = LocationService();
    PedometerService.instance.init();
    initializeLocation();
  }
  Future<void> initializeLocation() async {
    await MyApp.locationService?.getLocationOnchange(context);
    startLocationStream();
  }
  Future<void> startLocation() async {
    if (locationService != null) {
      await locationService!.getLocationOnchange(context);
      locationService!.getListener();
      startLocationStream();
    }
  }
  void resetLocationService() {
    locationService?.dispose();
    locationService = LocationService();
  }
  @override
  Widget build(BuildContext context) {
    if (widget.isLoggedIn ?? false) {
      if (locationService == null) {
        resetLocationService();
        startLocation();
      }
    } else {
      resetLocationService();
    }

    return MaterialApp(
      title: "Semdatex Patient Portal",
      debugShowCheckedModeBanner: false,
      theme: widget.isLightTheme ? lightMode : darkMode,
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      initialRoute: widget.homepage,
      home: PushMessaging(
        isLoggedIn: widget.isLoggedIn,
        homepage: widget.homepage,
        title: "Semdatex Patient Portal",
      ),
      routes: {
        '/questionnairePushNotificationRoot': (context) => const QuestionnaireRoot(),
        '/singleChoiceNew': (context) => const SelectionWindow(),
        '/exercisePrescriptionPage': (context) => const PrescriptionHome(),
        '/prescription': (context) => const PrescriptionHome(),
        '/multipleChoice': (context) => const MultipleChoice(),
        '/scale': (context) => const Scale(),
        '/textfield': (context) => const FreeTextfield(),
        '/endScreen': (context) => const EndScreen(),
        '/loginScreen': (context) => LoginScreen(locationService: locationService!),
        '/registerScreen': (context) => const RegisterScreen(),
        '/error_report': (context) => const PrescriptionHome(),
        '/actsessns': (context) => const ActivitiesLineChart(title: 'ActivitySessions'),
      },
    );
  }
}

RegisterScreen中跳转代码如下:

Future<void> _showMyDialog(text, statusCode) async {
    return showDialog<void>(
      context: context,
      barrierDismissible: false, // user must tap button!
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('alert'.tr()),
          content: SingleChildScrollView(
            child: ListBody(
              children: <Widget>[
                Text(text),
              ],
            ),
          ),
          actions: <Widget>[
            TextButton(
                child: Text(
                  'approve'.tr(),
                  style: const TextStyle(fontSize: 22),
                ),
                onPressed: () {
                  if (statusCode == 200) {
                    Navigator.pushNamed(context, '/loginScreen');
                  } else {
                    Navigator.of(context).pop();
                  }
                }),
          ],
        );
      },
    );
  }

尝试使用Navigator.of(context).pushNamed('/loginScreen')也未解决问题,请问如何正确跳转至LoginScreen页面?


解决方案

问题根源

  1. 上下文不匹配:弹窗AlertDialog的builder函数生成的是局部上下文,无法访问根MaterialApp的导航栈,导致调用pushNamed时找不到已注册的路由。
  2. 空指针隐患:当widget.isLoggedIn为false时,locationService会被重置,但路由配置中用!强制解包,若此时locationService未初始化完成,会触发空指针异常。

修复步骤

1. 使用根导航上下文跳转

有两种方式获取根导航器:

  • 方式一:全局导航键
    在MyApp中定义全局导航键:

    class MyApp extends StatefulWidget {
      static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
      // ... 其他代码
    }
    

    在MaterialApp中配置该键:

    return MaterialApp(
      navigatorKey: MyApp.navigatorKey,
      // ... 其他配置
    );
    

    修改弹窗跳转逻辑:

    onPressed: () {
      if (statusCode == 200) {
        MyApp.navigatorKey.currentState?.pushNamed('/loginScreen');
        Navigator.of(context).pop(); // 先关闭弹窗
      } else {
        Navigator.of(context).pop();
      }
    },
    
  • 方式二:指定根导航器
    直接在跳转时获取根导航上下文:

    onPressed: () {
      if (statusCode == 200) {
        Navigator.of(context, rootNavigator: true).pushNamed('/loginScreen');
        Navigator.of(context).pop();
      } else {
        Navigator.of(context).pop();
      }
    },
    

2. 修复locationService空值问题

修改resetLocationService确保实例始终非空:

void resetLocationService() {
  locationService?.dispose();
  locationService = LocationService(); // 直接初始化新实例
}

或者在路由配置中添加空值兜底:

'/loginScreen': (context) => LoginScreen(locationService: locationService ?? LocationService()),

3. 额外优化建议

  • 移除MaterialApp中的home参数,避免与initialRoute冲突(两者只需配置一个)。
  • 添加onUnknownRoute处理未注册路由,增强容错性:
    MaterialApp(
      // ... 其他配置
      onUnknownRoute: (settings) {
        return MaterialPageRoute(builder: (context) => const RegisterScreen());
      },
    );
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:55:53