为命名路由 + 传递参数创建 bloc 实例

Create bloc instance for named route + pass arguments

提问人:user1209216 提问时间:11/16/2023 最后编辑:Mark Rotteveeluser1209216 更新时间:11/17/2023 访问量:37

问:

我的应用使用 FlutterBloc 进行状态管理。我的用例:

  • 从 REST Web 服务加载的项目列表
  • 在每个项目上,点击我需要加载项目详细信息并将其显示在新页面上。

我的代码在列表项上显示详细信息页面,点击:

return GestureDetector(
      onTap: () {
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (_) => BlocProvider(
              create: (_) => DetailsBloc(
                someRepository: context.read<SomeRepository>(),
                otherRepository context.read<OtherRepository>(),
              )..add(GetItemDetailsEvent(id: item.id.toString())),
              child: const EventDetailsPage(),
            ),
          ),
        );
      },
      child: Card(....)

它工作正常。在每次点击列表项时,它都会显示新页面,并为其上下文提供新实例。页面关闭后,bloc 也将被销毁(这就是我希望它的工作方式)。 包括按项目 ID 加载详细信息数据的事件,因此详细信息窗口能够自行刷新以显示详细信息。DetailsBlocDetailsBloc

但我更喜欢使用命名路由并改用。当创建路由时,我能够以这种方式提供新实例,并可以选择向其发送事件:pushNamedDetailsBloc

'/item_details': (context) => BlocProvider(
            create: (context) => DataBloc(
                  someRepository: SomeRepository(),
                  otherRepository: OherRepository(),
                )..add(GetItemDetailsEvent(id: <id>),
                ,
               child: const DetailsPage()),

这样,我可以通过以下方式显示新页面 但在这种情况下,我认为无法将 id 传递给事件以加载数据,因为不知道路由何时初始化自身。所以我不能在路由定义中添加事件。我可以稍后尝试在列表项上添加事件:Navigator.of(context).pushNamed('/item_details');

onTap: () {
 context.read<DetailsBloc>().add((GetItemDetailsEvent(id: item.id.toString());

这显然是行不通的,因为这里没有提供,在创建路由之前,它甚至不存在。我还可以将事件添加到我的内部,但它必须在内部初始化状态下完成,因此:DetailsBlocDetailsBlocDetailsPage

  • 我的详细信息页面小部件需要是有状态的,我想避免
  • 即使详细信息页面小部件是有状态的,我也无法读取 init 状态中的路由参数以将其传递给 bloc 的事件。所以基本上,我被卡住了,在这种情况下,我认为没有办法使用命名路由。

您有什么想法如何在这里使用命名路由吗?

参数传递 flutter-bloc named-routing

评论


答:

1赞 Vladyslav Ulianytskyi 11/17/2023 #1

您可以在路由器参数中传递 id:

await Navigator.pushNamed(
                context,
                DetailsPage.routeName,
                arguments: id,
              );

然后在route_builder中:

...
case DetailsPage.routeName:
        final arguments = settings.arguments as String;
        return MaterialPageRoute(
          builder: (context) => DetailsPage(itemId: arguments),
          settings: settings,
        );        
...

和 DetailsPage 中的 finaly:

class DetailsPage extends StatelessWidget {
  const DetailsPage({
    required this.itemId,
    super.key,
  });

  static const routeName = '/details_page';
  final int storyId;

  @override
  Widget build(BuildContext context) => BlocProvider<DetailsBloc>(
        create: (context) => DetailsBloc(
            someRepository: SomeRepository(),//or get it from DI or ServiceLocator
            otherRepository: OtherRepository(),//or get it from DI or ServiceLocator
          )..add(GetItemDetailsEvent(id: item.id.toString())),
        child: const DetailsPageView(),
      );
}

评论

0赞 user1209216 11/18/2023
是的,这也是我的想法。但我不喜欢我被迫使用param代替,而且我被迫在覆盖中创建bloc实例onGenerateRouteroutesbuild
0赞 Vladyslav Ulianytskyi 11/18/2023
首先,创建 onGenerateRoute 是为了能够将参数传递给小部件。因此,正如您还提到的,您无法在您的情况下传递值,在创建路由之前不存在 DetailsBloc,并且您不希望它以这种方式运行。不太明白为什么你不喜欢 onGenerateRoute。作为拐杖:)您始终可以将 ID 存储在 SharedPreferences 中,并在需要时检索它:)
0赞 user1209216 11/18/2023
you always could store id in sharedpreferences and retrive it when you need it - 这没有任何意义,因为每个列表项选项卡上的 ID 都会发生变化。你是对的,路线并没有那么糟糕。但是细节小部件比较复杂+在build方法中创建bloc实例非常糟糕,因为build方法不能保证只执行一次onGenerate
0赞 Vladyslav Ulianytskyi 11/19/2023
有点很奇怪的说法:“在构建方法中创建 bloc 实例非常糟糕,因为构建方法不保证只执行一次”从这个逻辑中我们得到:在父级的构建方法中创建任何 WIDGET 实例都是非常糟糕的,因为构建方法不保证只执行一次 %:) BlocBuilder 只是一个 Flutter 小部件。更多的信息: bloclibrary.dev/#/flutterbloccoreconcepts
0赞 user1209216 11/19/2023
是的,它是。但我实际上不想在每个小部件构建上创建新状态,因为我只需要向上下文提供和注入 bloc 一次。你真的不觉得在构建方法中创建新的 bloc 实例不安全吗?在我看来 - 获取价值:好的,创建实例 - 不是真的。最糟糕的场景:创建 bloc 实例 + 添加重建小部件的事件,我们得到无限循环