hightman / getx5-example_nav2

A new Flutter project use Nav2.0 with nested routing.
3 stars 0 forks source link

感谢大佬的example,有几个GetX 5的嵌套路由的问题还想请教下 #4

Open Ssiswent opened 1 month ago

Ssiswent commented 1 month ago

首先,非常感谢您,找了很久嵌套路由状态保持的方法,终于在大佬这里看到解决方法了!

我在getx的issue里面提了几个问题,请问大佬是否有遇到或者知道怎么解决吗: https://github.com/jonataslaw/getx/issues/3168 https://github.com/jonataslaw/getx/issues/3170 https://github.com/jonataslaw/getx/issues/3175

然后下面是我的部分路由配置,请问是否有问题呢,因为我发现这样写,几乎所有的页面都要加上participatesInRootNavigator: true, 否则会显示不出来或者继承上一个页面的AppBar、BottomNavBar之类的:

app_pages.dart:

static final routes = [
    GetPage(
      middlewares: [EnsureAuthMiddleware(), MainMiddleware()],
      name: _Paths.main,
      page: () => const MainPage(),
      binding: MainBinding(),
      transition: Transition.topLevel,
      participatesInRootNavigator: true,
      children: [
        GetPage(
          name: _Paths.channelList,
          page: () => const ChannelListPage(),
          binding: ChannelListBinding(),
        ),
        GetPage(
          name: _Paths.userMentions,
          page: () => const UserMentionsPage(),
          binding: UserMentionsBinding(),
        ),
        GetPage(
          name: _Paths.channel,
          page: () => ChannelPage(),
          // binding: ChannelBinding(),
          preventDuplicates: false,
          participatesInRootNavigator: true,
        ),
        GetPage(
          name: _Paths.admin,
          page: () => const AdminPage(),
          binding: AdminBinding(),
          participatesInRootNavigator: true,
          children: [
            GetPage(
              name: _Paths.createUser,
              page: () => const CreateUserPage(),
              binding: CreateUserBinding(),
              participatesInRootNavigator: true,
              children: [
                GetPage(
                  name: _Paths.selectGroups,
                  page: () => const SelectGroupsPage(),
                  binding: SelectGroupsBinding(),
                  participatesInRootNavigator: true,
                ),
              ],
            ),
            GetPage(
              name: _Paths.exportUsers,
              page: () => const ExportUsersPage(),
              binding: ExportUsersBinding(),
              participatesInRootNavigator: true,
            ),
          ],
        ),
      ],
    ),
    GetPage(
      middlewares: [EnsureNotAuthMiddleware()],
      name: _Paths.login,
      page: () => LoginPage(),
      binding: LoginBinding(),
    ),
    GetPage(
      middlewares: [ChangePasswordMiddleware()],
      name: _Paths.changePassword,
      page: () => ChangePasswordPage(),
      binding: ChangePasswordBinding(),
    ),
  ];

app_routes.dart:

abstract class Routes {
  static const main = _Paths.main;
  static const login = _Paths.login;
  static const changePassword = _Paths.changePassword;

  static const channelList = _Paths.main + _Paths.channelList;
  static const userMentions = _Paths.main + _Paths.userMentions;

  static const channel = _Paths.main + _Paths.channel;

  static const admin = _Paths.main + _Paths.admin;

  static const createUser = _Paths.main + _Paths.admin + _Paths.createUser;
  static const selectGroups = _Paths.main + _Paths.admin + _Paths.createUser + _Paths.selectGroups;

  static const exportUsers = _Paths.main + _Paths.admin + _Paths.exportUsers;
}

abstract class _Paths {
  static const main = '/main';
  static const login = '/login';
  static const changePassword = '/changePassword';

  static const channelList = '/channelList';
  static const userMentions = '/userMentions';

  static const channel = '/channel';

  static const admin = '/admin';
  static const createUser = '/createUser';

  static const exportUsers = '/exportUsers';
  static const selectGroups = '/selectGroups';
}

main_page.dart

class MainPage extends GetView<MainController> {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context) {
    final user = StreamChat.of(context).currentUser;
    if (user == null) {
      return const Offstage();
    }
    return GetBuilder<MainController>(
      builder: (_) {
        return FocusDetector(
          onFocusGained: () => ThemeService.to.changeNavigationBarColor(isMainPage: true),
          onFocusLost: ThemeService.to.changeNavigationBarColor,
          child: CustomWillPopScope(
            child: GetRouterOutlet.builder(
              route: Routes.main,
              builder: (context) {
                return Scaffold(
                  appBar: CustomChannelListHeader(
                    titleBuilder: (context, status, client) => ChatListHeaderTitle(
                      status: status,
                      client: client,
                    ),
                    actions: [
                      StorageService().isFinnplayAdmin
                          ? NewChatButton(onPressed: () => Get.toNamed(Routes.newChat))
                          : const SizedBox()
                    ],
                    preNavigationCallback: () => FocusScope.of(context).requestFocus(FocusNode()),
                  ),
                  drawer: LeftDrawer(
                    user: user,
                    controller: controller,
                  ),
                  drawerEdgeDragWidth: 50,
                  body: GetRouterOutlet(
                    initialRoute: Routes.channelList,
                    anchorRoute: Routes.main,
                    filterPages: (pages) {
                      var ret = pages.toList();
                      if (ret.isEmpty && ModalRoute.of(context)!.isCurrent) {
                        ret.add(context.delegate.matchRoute(Routes.channelList).route!);
                      }
                      final nav = Get.nestedKey(Routes.main)?.navigatorKey.currentState?.widget;
                      Get.log('Home filter pages: ${pages.map((e) => e.name)}');

                      if (nav != null) {
                        if (ret.isEmpty) {
                          Get.log("Home use olds: ${nav.pages.map((e) => e.name)}");
                          return nav.pages as List<GetPage>;
                        }
                        final sn = ret[0].name.split('/').length;
                        for (var p in nav.pages as List<GetPage>) {
                          if (p.maintainState && p.name.split('/').length == sn && !ret.contains(p)) {
                            ret.insert(0, p);
                          }
                        }
                      }
                      ret = ret.where((e) => e.participatesInRootNavigator != true).toList();
                      Get.log('Home real pages: ${ret.map((e) => e.name)}');
                      return ret;
                    },
                  ),
                  bottomNavigationBar: IndexedRouteBuilder(
                      routes: const [
                        Routes.channelList,
                        Routes.userMentions,
                      ],
                      builder: (context, routes, index) {
                        final delegate = context.delegate;
                        return NavigationBar(
                          indicatorColor: Colors.transparent,
                          selectedIndex: index,
                          onDestinationSelected: (value) => delegate.toNamed(routes[value]),
                          destinations: [
                            NavigationDestination(
                              icon: Stack(
                                clipBehavior: Clip.none,
                                children: [
                                  SvgPicture.asset('assets/icons/bottom_navigation_chats_unselected.svg'),
                                  const Positioned(
                                    top: -3,
                                    right: -16,
                                    child: UnreadIndicator(),
                                  ),
                                ],
                              ),
                              selectedIcon: Stack(
                                clipBehavior: Clip.none,
                                children: [
                                  SvgPicture.asset('assets/icons/bottom_navigation_chats_selected.svg'),
                                  const Positioned(
                                    top: -3,
                                    right: -16,
                                    child: UnreadIndicator(),
                                  ),
                                ],
                              ),
                              label: 'Chats',
                            ),
                            NavigationDestination(
                              icon: SvgPicture.asset('assets/icons/bottom_navigation_me_unselected.svg'),
                              selectedIcon: SvgPicture.asset('assets/icons/bottom_navigation_me_selected.svg'),
                              label: 'Me',
                            ),
                          ],
                        );
                      }),
                );
              },
            ),
          ),
        );
      },
    );
  }
}
Ssiswent commented 1 month ago

另外请问您example的main.dart中的_createDelegate是什么作用了,我试了下不实现routerDelegate也是可以保持状态的,而且在我的project中,使用了这个_createDelegate会出现以下错误:

Launching lib/main.dart on Chrome in debug mode...
This app is linked to the debug service: ws://127.0.0.1:61585/2emmxbi6_ik=/ws
Debug service listening on ws://127.0.0.1:61585/2emmxbi6_ik=/ws
Connecting to VM Service at ws://127.0.0.1:61585/2emmxbi6_ik=/ws
Connected to the VM Service.

starting services ...
All services started...
[GETX] Instance "AppService" has been created
[GETX] Instance "AppService" has been initialized
[GETX] Instance "AuthService" has been created
[GETX] Instance "AuthService" has been initialized
[GETX] Instance "ThemeService" has been created
[GETX] Instance "ThemeService" has been initialized
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building Builder(dirty, dependencies: [Binder<AppController>]):
Assertion failed: org-dartlang-sdk:///lib/ui_web/ui_web/navigation/url_strategy.dart:53:5
_customUrlStrategyCanBeSet
"Cannot set URL strategy a second time or after the app has been initialized."

The relevant error-causing widget was:
  GetBuilder<AppController>
  GetBuilder:file:///Users/ssiswent/Documents/Finnplay/finnplay-chat-client/lib/app/app.dart:19:12

When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 296:3  throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 29:3   assertFailed
lib/ui_web/ui_web/navigation/url_strategy.dart 53:5                          set urlStrategy
packages/flutter_web_plugins/src/navigation/url_strategy.dart 22:10          setUrlStrategy
packages/get/get_navigation/src/routes/url_strategy/web/web_url.dart 4:3     removeHash
packages/get/get_navigation/src/routes/url_strategy/url_strategy.dart 4:3    setUrlStrategy
packages/get/get_navigation/src/routes/get_router_delegate.dart 100:46       new
packages/finnplay_chat/app/app.dart 80:10                                    _createDelegate
packages/finnplay_chat/app/app.dart 42:31                                    <fn>
packages/get/get_state_manager/src/simple/get_state.dart 82:23               <fn>
packages/flutter/src/widgets/basic.dart 7688:48                              build
packages/flutter/src/widgets/framework.dart 5687:22                          build
packages/flutter/src/widgets/framework.dart 5617:15                          performRebuild
packages/flutter/src/widgets/framework.dart 5333:7                           rebuild
packages/flutter/src/widgets/framework.dart 2693:14                          [_tryRebuild]
packages/flutter/src/widgets/framework.dart 2752:11                          [_flushDirtyElements]
packages/flutter/src/widgets/framework.dart 3048:17                          buildScope
packages/flutter/src/widgets/binding.dart 1162:9                             drawFrame
packages/flutter/src/rendering/binding.dart 468:5                            [_handlePersistentFrameCallback]
packages/flutter/src/scheduler/binding.dart 1397:7                           [_invokeFrameCallback]
packages/flutter/src/scheduler/binding.dart 1318:9                           handleDrawFrame
packages/flutter/src/scheduler/binding.dart 1040:9                           <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:11      internalCallback

════════════════════════════════════════════════════════════════════════════════════════════════════
[GETX] Instance "AppController" has been created
[GETX] Instance "AppController" has been initialized
[GETX] GetDelegate is created !
[GETX] GetInformationParser is created !
Another exception was thrown: "ThemeService" not found. You need to call "Get.put(ThemeService())" or "Get.lazyPut(()=>ThemeService())"

image

image

以下是我的App代码:

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

  @override
  Widget build(BuildContext context) {
    return GetBuilder<AppController>(
      init: AppController(),
      builder: (ctrl) {
        return OKToast(
          child: FocusDetector(
            // onForegroundGained: () => ctrl.runningBackground(false),
            // onForegroundLost: () => ctrl.runningBackground(true),
            child: GetMaterialApp.router(
              title: 'Finnplay Chat',
              theme: ThemeConfig.light,
              darkTheme: ThemeConfig.dark,
              themeMode: ThemeService.to.themeMode,
              supportedLocales: const [
                Locale('en'),
              ],
              localizationsDelegates: const [
                AppLocalizationsDelegate(),
                GlobalStreamChatLocalizations.delegate,
                GlobalMaterialLocalizations.delegate,
                GlobalWidgetsLocalizations.delegate,
              ],
              getPages: AppPages.routes,
              // initialRoute: Routes.main,
              routerDelegate: _createDelegate(),
              // initialBinding: InitBinding(),
              // navigatorKey: ctrl.navigatorKey,
              navigatorObservers: [
                if (ctrl.localNotificationObserver != null) ctrl.localNotificationObserver!,
                FirebaseAnalyticsObserver(analytics: ctrl.analytics),
              ],
              builder: (context, widget) => Stack(
                alignment: Alignment.center,
                children: [
                  if (ctrl.client != null)
                    StreamChat(
                      useMaterial3: true,
                      streamChatThemeData: ThemeService.to.isDarkMode
                          ? ThemeConfig.streamChatThemeDarkData
                          : ThemeConfig.streamChatThemeLightData,
                      client: ctrl.client!,
                      child: widget,
                    ),
                  if (!ctrl.animationCompleted) ctrl.buildAnimation(),
                ],
                // builder: EasyLoading.init(),
              ),
            ),
          ),
        );
      },
    );
  }
}

还请大佬有空的话解答一下,感激不尽!

hightman commented 3 weeks ago

自定义 createDelegate 的目的是个性化编写 pickPagesForRootNavigator,默认实现存在很多缺限