如何正确使用Flutter的“go_router”软件包来利用子路由?

huangapple go评论56阅读模式
英文:

How do I utilize subroutes with Flutter's "go_router" package properly?

问题

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 these 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 you're using.

英文:

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: () =&gt; context.go(&#39;userlist/$userID&#39;),
        ),
      ),
    );
  }
}

DetailsPage:

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

  final String? userId;

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

    final User? user = getUserById(userId);

    if (user == null) {
      return const Text(&#39;User not found&#39;);
    }

    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: () =&gt; context.go(&#39;/userlist&#39;),
                icon: const Icon(Icons.arrow_back)),
          ],
        ),
      ],
    );
  }
}

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

final routeProvider = Provider&lt;GoRouter&gt;((ref) =&gt; GoRouter(
  errorBuilder: (context, state) =&gt; const ErrorPage(),
  routes: &lt;RouteBase&gt;[
      GoRoute(
        path: &#39;/&#39;,
        pageBuilder: (context, state) =&gt; const MaterialPage(child: HomePage()),
      ),
      GoRoute(
          path: &#39;/userlist&#39;,
          pageBuilder: (context, state) {
            return const MaterialPage(child: UserList());
          },
          routes: [
            GoRoute(
                path: &#39;userlist/:userId&#39;,
                pageBuilder: (context, state) {
                  final userId = state.pathParameters[&#39;userId&#39;];
                  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.

答案1

得分: 1

导航必须使用完整路径。在您的`build`方法中,请参阅注释。

```dart
@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'), // <= 添加了'/'字符。
        ),
      ),
    );
  }

路由代码:

子路由不应包含其父路径,这由GoRouter自动完成。请参阅注释的代码。

GoRoute(
  path: '/userlist',
  pageBuilder: (context, state) {
      return const MaterialPage(child: UserList());
  },
  routes: [
      GoRoute(
          /// 子路由不得具有父路由路径。
          path: ':userId', // <= 删除了'userlist/'。
          pageBuilder: (context, state) {
              final userId = state.pathParameters['userId'];
              return MaterialPage(child: DetailsPage(userId: userId));
          }),
      ])
英文:

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 &#39;/&#39; character.
          onTap: () =&gt; context.go(&#39;/userlist/$userID&#39;), // &lt;= Added &#39;/&#39; character.
        ),
      ),
    );
  }

Routing code:

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

GoRoute(
  path: &#39;/userlist&#39;,
  pageBuilder: (context, state) {
      return const MaterialPage(child: UserList());
  },
  routes: [
      GoRoute(
          /// Sub-route must not have the parent route path.
          path: &#39;:userId&#39;, // &lt;= Removed &#39;userlist/&#39;.
          pageBuilder: (context, state) {
              final userId = state.pathParameters[&#39;userId&#39;];
              return MaterialPage(child: DetailsPage(userId: userId));
          }),
      ])

huangapple
  • 本文由 发表于 2023年5月25日 22:14:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76333272.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定