(ИСКЛЮЧЕНИЕ ПОЛУЧЕНО GOROUTER) Неудачное утверждение: строка 299 поз. 13: '!redirects.contains(redir)': обнаружена петля перенаправления:

Я пытаюсь использовать GoRoute, Stream и Bloc. Итак, я реализую Auth Process. Таким образом, если пользователь не вошел в систему, он не может получить доступ ни к одной странице. И `Stream позволяет постоянно прослушивать AuthStatus (состояние блока). Но так как я не уверен, почему, но я получаю эту ошибку

      Exception: 'package:go_router/src/go_router_delegate.dart':
Failed assertion: line 299 pos 13: '!redirects.contains(redir)':
redirect loop detected:

Я попытался создать фиктивное приложение, чтобы понять, что именно происходит не так. Но так и не смог понять в чем причина. Чуть ниже я прикрепляю код, всего около 200 строк, любой может скопировать и вставить его, а затем запустить команду и на экране появляется ошибка приложения.

      import 'dart:async';

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

enum Pages {
  home,
  profile,
  settings,
}

class FooStream {
  final StreamController<Pages> _controller;
  FooStream() : _controller = StreamController.broadcast();

  Stream<Pages> get stream {
    final listOfPages = [Pages.home, Pages.profile, Pages.settings];
    //  every 3 seconds loop over enum and emit a new value to the stream

    Timer.periodic(const Duration(seconds: 3), (timer) {
      final index = timer.tick ~/ 3;
      if (index >= listOfPages.length) {
        timer.cancel();
      } else {
        _controller.add(listOfPages[index]);
      }
    });

    return _controller.stream;
  }

  void update(Pages page) {
    _controller.add(page);
  }

  void close() {
    _controller.close();
  }

  void dispose() {
    _controller.close();
  }
}

class FooNotifier extends ChangeNotifier {
  final ValueNotifier<Pages> _page = ValueNotifier(Pages.home);

  ValueNotifier<Pages> get page => _page;

  // listen to the stream and whenever it emits a new value, update the page
  final FooStream stream;
  FooNotifier(this.stream) {
    stream.stream.listen((Pages page) {
      _page.value = page;
      notifyListeners();
    });
  }

  void close() {
    notifyListeners();
  }

  @override
  void dispose() {
    notifyListeners();
    super.dispose();
  }
}

// my home page
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
          child: Text(
        "Home",
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
      )),
    );
  }
}

// profile page
class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
          child: Text(
        "Profile",
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
      )),
    );
  }
}

// setting page
class SettingPage extends StatelessWidget {
  const SettingPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
          child: Text(
        "Setting",
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
      )),
    );
  }
}

class FooRoute {
  final FooNotifier notifier;
  FooRoute(this.notifier);
  GoRouter routeTo() {
    return GoRouter(
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => const MyHomePage(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => const ProfilePage(),
        ),
        GoRoute(
          path: '/setting',
          builder: (context, state) => const SettingPage(),
        ),
      ],
      refreshListenable: notifier,
      redirect: safePage,
      debugLogDiagnostics: true,
    );
  }

  // on page change, return the new page
  String? safePage(GoRouterState state) {
    final newPage = notifier.page.value;

    // if the new page is the same as the current page, do nothing
    if (newPage == Pages.home) {
      return '/';
    }

    if (newPage == Pages.profile) {
      return '/profile';
    }

    if (newPage == Pages.settings) {
      return '/settings';
    }

    return null;
  }
}

class FooApp extends StatelessWidget {
  FooApp({
    Key? key,
  }) : super(key: key);

  final _router = FooRoute(FooNotifier(FooStream())).routeTo();

  // base the

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<Pages>(
      valueListenable: FooNotifier(FooStream()).page,
      builder: (context, value, child) {
        return MaterialApp.router(
          routerDelegate: _router.routerDelegate,
          routeInformationParser: _router.routeInformationParser,
        );
      },
    );
  }
}

void main(List<String> args) {
  runApp(FooApp());
}

Я также прикрепляю сообщение, которое я получаю в терминале. Как только приведенный выше код запустится.

      Launching lib\main.dart on Windows in debug mode...
package:bug_test/main.dart:1
Connecting to VM Service at ws://127.0.0.1:63905/cNY-SYgW7KE=/ws
[GoRouter] known full paths for routes:
[GoRouter]   => /
[GoRouter]   => /profile
[GoRouter]   => /setting
[GoRouter] setting initial location /
flutter: ══╡ EXCEPTION CAUGHT BY GOROUTER ╞══════════════════════════════════════════════════════════════════
flutter: The following _Exception was thrown Exception during GoRouter navigation:
flutter: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13:
package:go_router/src/go_router_delegate.dart:299
flutter: '!redirects.contains(redir)': redirect loop detected: / => /
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #2      GoRouterDelegate._getLocRouteMatchesWithRedirects.redirected
package:go_router/src/go_router_delegate.dart:299
flutter: #3      GoRouterDelegate._getLocRouteMatchesWithRedirects
package:go_router/src/go_router_delegate.dart:322
flutter: #4      GoRouterDelegate._go
package:go_router/src/go_router_delegate.dart:245
flutter: #5      new GoRouterDelegate
package:go_router/src/go_router_delegate.dart:58
flutter: #6      new GoRouter
package:go_router/src/go_router.dart:46
flutter: #7      FooRoute.routeTo
package:bug_test/main.dart:122
flutter: #8      new FooApp
package:bug_test/main.dart:169
flutter: #9      main
flutter: #10     _runMain.<anonymous closure> (dart:ui/hooks.dart:132:23)
flutter: #11     _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
flutter: (elided 3 frames from class _AssertionError and class _RawReceivePortImpl)
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /
package:go_router/src/go_router_delegate.dart:299
[GoRouter] MaterialApp found
[GoRouter] refreshing /
2
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /profile => /profile
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
[GoRouter] redirecting to /profile
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /profile => /profile
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
flutter: Another exception was thrown: Exception: 'package:go_router/src/go_router_delegate.dart': Failed assertion: line 299 pos 13: '!redirects.contains(redir)': redirect loop detected: / => /settings => /settings
package:go_router/src/go_router_delegate.dart:299
[GoRouter] refreshing /
[GoRouter] redirecting to /settings
Application finished.
Exited (sigterm)

Я был бы очень благодарен, если кто-то может мне помочь. Спасибо

ОБНОВИТЬ

Я поднял эту проблему в официальном репозитории Flutter на GitHub, вот ссылка, пожалуйста, проверьте ссылку ниже https://github.com/flutter/flutter/issues/104441

2 ответа

Публикую краткий ответ. Если это поможет вам в любом случае, я полностью опишу, как я перемещался по этой проблеме.

Короче говоря:

Функция перенаправления go_router почему-то запускается дважды. По этой причине я настроил свои условия if/else таким образом, чтобы они всегда перенаправляли меня в желаемое место назначения.

      redirect: (state) {  
      final isLogging = state.location == '/login';
      final isLoggingByPhoneNumber =
          state.location == '/login/loginUsingPhoneNumber';
      final isSigning = state.location == '/signup';         

      if (!userAuthDao.isLoggedIn()) { //If user is not logged in.
        return isLogging
            ? null
            : isSigning
                ? null
                : isLoggingByPhoneNumber
                    ? null
                    : '/login';
      }

      final isLoggedIn = state.location == '/';
      if (userAuthDao.isLoggedIn() && isLogging) return isLoggedIn ? null : '/';
      if (userAuthDao.isLoggedIn() && isSigning) return isLoggedIn ? null : '/';
      if (userAuthDao.isLoggedIn() && isLoggingByPhoneNumber) {
        return isLoggedIn ? null : '/';
      }

      return null;  //if none of the conditions are triggered, you will be routed to where ever you were going.
    },

Гораздо более сложная логика маршрутизации:

      redirect: (state) {
      final isLogging = state.location == '/login';
      final isInitializing = state.location == '/0';
      final isOnboarding = state.location == '/onboarding';

      final isGoingProfilePage = state.location == '/profile';

      if (!appStateManager.isInitialized) return isInitializing ? null : '/0';

      if (appStateManager.isInitialized && !appStateManager.isLoggedIn)
        return isLogging ? null : '/login';

      if (appStateManager.isLoggedIn && !appStateManager.isOnboardingComplete)
        return isOnboarding ? null : '/onboarding';

      x = appStateManager.getSelectedTab;
      final isGoingHome = state.location == '/home/tab/${x}';

      if (appStateManager.isOnboardingComplete &&
          !profileManager.didSelectUser &&
          !groceryManager.isCreatingNewItem &&
          !(groceryManager.selectedIndex != -1))
        return isGoingHome
            ? null
                : '/home/tab/${x}';    

      final isGoingToCreate = state.location == '/home/tab/2/newitem';

      if (groceryManager.isCreatingNewItem)
        return isGoingToCreate ? null : '/home/tab/${x}/newitem';

      final isViewingExistingItem =
          state.location == '/home/tab/2/item/${groceryManager.selectedIndex}';

      if (groceryManager.selectedIndex != -1)
        return isViewingExistingItem
            ? null
            : '/home/tab/2/item/${groceryManager.selectedIndex}';

      if (profileManager.didSelectUser)
        return isGoingProfilePage ? null : '/profile';

      return null;
},

Я думаю, вы неправильно поняли redirectпараметр GoRouter.
Этот атрибут прослушивает каждое изменение маршрута и был сделан для облегчения перенаправления при определенных обстоятельствах (например, пользователь отключен, поэтому вы перенаправляете его на страницу /login).

Официальная документация объясняет это очень хорошо.

Удаление этого параметра устраняет вашу проблему.

      GoRouter routeTo() {
    return GoRouter(
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => const MyHomePage(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => const ProfilePage(),
        ),
        GoRoute(
          path: '/setting',
          builder: (context, state) => const SettingPage(),
        ),
      ],
      refreshListenable: notifier,
      debugLogDiagnostics: true,
    );
  }

и вместо того, чтобы использовать этот метод для создания навигации, вы должны использовать GoRouter.of(context).go('/profile')как объяснено здесь

Другие вопросы по тегам