0

I'm trying to navigate to a "DetailsPage" by clicking on a user's correspondent ListTile, within a ListView.builder.

But when I click on the tile I get theses exceptions:

  • Exception: Match error found during build phase
  • Exception: no routes for location: userlist/1

It's important to note that the navigation from the home page ("/") to the UserList ("/userlist") is fine, and that the DetailsPage is in a subroute of the UserList.

For the relevant code, here are a few snippets:

ListTile:

class CustomListTile extends ConsumerWidget {
  const CustomListTile({
    super.key,
    required this.title,
    required this.subtitle,
    required this.userID,
  });

  final String title;
  final String subtitle;
  final String userID;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Material(
        child: ListTile(
          leading: const Icon(Icons.person),
          title: Text(title),
          trailing: Text(userID),
          subtitle: Text(subtitle),
          hoverColor: Colors.black12,
          onTap: () => context.go('userlist/$userID'),
        ),
      ),
    );
  }
}

DetailsPage:

class DetailsPage extends ConsumerWidget {
  const DetailsPage({required this.userId, super.key});

  final String? userId;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final AsyncValue<List<User>> pageUserListProvider = ref.watch(userListProvider);
    User? getUserById(String? userId) {
      return pageUserListProvider.when(
        data: (userList) {
          return userList.firstWhere((user) => user.id == userId);
        },
        loading: () => null,
        error: (error, stackTrace) => null,
      );
    }

    final User? user = getUserById(userId);

    if (user == null) {
      return const Text('User not found');
    }

    return ListView(
      children: [
        Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            const Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [Icon(Icons.person, size: 50.0)]),
            const SizedBox(height: 20),
            Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [Text(user.name)]),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                const SizedBox(width: 10),
                Text(user.age.toString()),
                Text(user.nationality),
              ],
            ),
            IconButton(
                onPressed: () => context.go('/userlist'),
                icon: const Icon(Icons.arrow_back)),
          ],
        ),
      ],
    );
  }
}

Routing code (it's within a Riverpod provider):

final routeProvider = Provider<GoRouter>((ref) => GoRouter(
  errorBuilder: (context, state) => const ErrorPage(),
  routes: <RouteBase>[
      GoRoute(
        path: '/',
        pageBuilder: (context, state) => const MaterialPage(child: HomePage()),
      ),
      GoRoute(
          path: '/userlist',
          pageBuilder: (context, state) {
            return const MaterialPage(child: UserList());
          },
          routes: [
            GoRoute(
                path: 'userlist/:userId',
                pageBuilder: (context, state) {
                  final userId = state.pathParameters['userId'];
                  return MaterialPage(child: DetailsPage(userId: userId));
                }),
          ]),
    ]));

Basically I was expecting the DetailsPage to load with the data of the corresponding user from the mock API I'm using.

  • Also i have notices when you make changes the go route class you have to do a full rebuild rather than a hot reload. – CStark May 25 '23 at 15:03

1 Answers1

1

ListTile:

Navigation must use the full path. Inside your build method, see the comments.

@override
  Widget build(BuildContext context, WidgetRef ref) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Material(
        child: ListTile(
          leading: const Icon(Icons.person),
          title: Text(title),
          trailing: Text(userID),
          subtitle: Text(subtitle),
          hoverColor: Colors.black12,
          
          /// Navigation must specify full path starting with '/' character.
          onTap: () => context.go('/userlist/$userID'), // <= Added '/' character.
        ),
      ),
    );
  }

Routing code:

Sub-routes should not include their parent path, that is done automatically by GoRouter. See the commented code.

GoRoute(
  path: '/userlist',
  pageBuilder: (context, state) {
      return const MaterialPage(child: UserList());
  },
  routes: [
      GoRoute(
          /// Sub-route must not have the parent route path.
          path: ':userId', // <= Removed 'userlist/'.
          pageBuilder: (context, state) {
              final userId = state.pathParameters['userId'];
              return MaterialPage(child: DetailsPage(userId: userId));
          }),
      ])
offworldwelcome
  • 1,314
  • 5
  • 11