将 BlocListener 与 Navigator PushNamed 一起使用会导致歧义

Posted

技术标签:

【中文标题】将 BlocListener 与 Navigator PushNamed 一起使用会导致歧义【英文标题】:Using BlocListener with Navigator PushNamed is causing ambiguity 【发布时间】:2021-09-09 05:00:18 【问题描述】:

我在使用 bloc 侦听器进行导航时遇到问题

我有五个用于导航的屏幕,我在每个文件中都使用 BlocConsumer。我在颤振检查器中寻找粒度视图。我正在使用 blocListener 进行导航。当我 pushNamed 第一个屏幕一切正常,我导航到第二个屏幕(第一个屏幕添加到导航堆栈)。现在我在第二屏,当我按下移动到第三屏时,两个第二屏然后添加第三屏,堆栈应该是这样的(第一屏,第二屏,第三屏)但不幸的是就像 (第 1 个屏幕,第 2 个屏幕,第 2 个屏幕,第 3 个屏幕)。现在当我在第 3 个屏幕上并想添加第 4 个屏幕是堆栈但添加了第 2 个屏幕时,第 3 个屏幕添加了两个次然后添加第 4 个屏幕。堆栈应该是这样的 ( 1st screen , 2nd screen , 3rd screen , 4th screen ) 但不幸的是它就像 ( 1st screen , 2nd screen , 2nd screen , 3rd screen , 2nd screen ,第三屏,第三屏,第四屏)。因此,我在导航堆栈中有 8 个屏幕,而不是 4 个屏幕。

这是我在所有文件中使用的模式。

这是我创建 bloc 实例并关闭它的地方。

class MyAppRoutes 
  FieldsBloc _fieldsBloc = FieldsBloc();

  Route onGenerateRoute(RouteSettings routeSettings) 
    try 
      switch (routeSettings.name) 

        case LandingPage.routeName:
          return MaterialPageRoute(builder: (_) => LandingPage());

        case CategoryPage.routeName:
          return MaterialPageRoute(
              builder: (context) => BlocProvider.value(
                    value: _fieldsBloc,
                    child: CategoryPage(),
                  ));

        case ExpertisePage.routeName:
          return MaterialPageRoute(
              builder: (context) => BlocProvider.value(
                    value: _fieldsBloc,
                    child: ExpertisePage(),
                  ));

        case ExpertiseLevelPage.routeName:
          return MaterialPageRoute(
              builder: (context) => BlocProvider.value(
                    value: _fieldsBloc,
                    child: ExpertiseLevelPage(),
                  ));

        case EducationPage.routeName:
          return MaterialPageRoute(
              builder: (context) => BlocProvider.value(
                    value: _fieldsBloc,
                    child: EducationPage(),
                  ));

        default:
          return null;
      
     catch (e) 
      print(e);
    
  

  void dispose() async 
    _fieldsBloc.close();
  

这是我在每个文件中使用的小部件。

BlocConsumer<FieldsBloc, FieldsState>(builder: (context, state) 
              if (state is FieldsInitial) 
                return Container();
               else if (state is FieldLoadingState) 
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: getCircularProgress(context),
                );
               else if (state is FieldSuccessfulState) 
                return Container();
               else if (state is FieldUnsuccessfulState) 
                return Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(
                          Icons.error,
                          color: Colors.red,
                        ),
                        SizedBox(
                          width: 5.0,
                        ),
                        Expanded(
                            child: TextStyleRes.textStyleFont1(
                                textColor: Colors.red,
                                text: state.message,
                                fontSize: 12,
                                fontWeight: FontWeight.w700)),
                      ],
                    ));
              
              return Container();
            , listener: (context, state) 
              if (state is FieldSuccessfulState)
                return SchedulerBinding.instance.addPostFrameCallback((_) 
                  Navigator.of(context).pushNamed(ExpertisePage.routeName);
                );
            ),

这是正在触发的 Bloc 事件。

abstract class FieldsEvent 

class NextButtonEventScreen3 extends FieldsEvent 
  List<String> categories;

  NextButtonEventScreen3(this.categories);


class NextButtonEventScreen4 extends FieldsEvent 
  List skills;

  NextButtonEventScreen4(this.skills);


class NextButtonEventScreen5 extends FieldsEvent 
  String expert;

  NextButtonEventScreen5(this.expert);

这是集团。

if (event is NextButtonEventScreen3) 
       if (event.categories.isNotEmpty) 
         yield FieldLoadingState();
         categories = event.categories;

         yield FieldSuccessfulState();
        else
         throw ('Please choose at least 1 category');
     
     //=====================SignUpScreen4===========================
     else if (event is NextButtonEventScreen4) 
       if (event.skills.isNotEmpty) 
         yield FieldLoadingState();
         skills = event.skills;

         yield FieldSuccessfulState(_updateModel());
        else
         throw ('Please provide at least 1 skill');
     
     //=====================SignUpScreen5===========================
     else if (event is NextButtonEventScreen5) 
       expert = event.expert;

       yield FieldSuccessfulState();
      

这些是集团中的国家

abstract class FieldsState 
 String message;


class FieldsInitial extends FieldsState 
class FieldLoadingState extends FieldsState 


class FieldSuccessfulState extends FieldsState 
 var data;

 FieldSuccessfulState([this.data]);



class FieldUnsuccessfulState extends FieldsState 
 String message;

 FieldUnsuccessfulState(this.message);

【问题讨论】:

这是单个FieldsBloc 服务所有三个屏幕吗? 是实际上 bloc 是相同的,但 blocConsumer 小部件在 5 个文件中被使用了 5 次 【参考方案1】:

导航堆栈上的先前页面仍然存在,正在监听 bloc 事件。当状态在第二个屏幕上变为FieldsSuccessfulState 时,两个侦听器都会看到这一点并尝试导航到下一个屏幕。

为确保只有当前屏幕会对FieldsSuccessfulState 做出反应,我可以想到两个选项:

FieldsSuccessfulState 拆分为多个结果(不同的类或添加的字段),并使每个屏幕仅对其自身的成功状态作出反应。

在导航到下一个屏幕之前检查ModalRoute.of(context).isCurrent。这可以在侦听器本身或listenWhen 参数中完成。

【讨论】:

以上是关于将 BlocListener 与 Navigator PushNamed 一起使用会导致歧义的主要内容,如果未能解决你的问题,请参考以下文章

测试小部件时在颤动中测试 BlocListener 失败

在 Cubit 函数调用后未执行 BlocListener

为啥 Flutter 中的 BloCListener 不保存状态,认证过程中出现 setState() 问题?

Flutter 在 MultiBlocProvider 中使用 BlocListener 和 BlocBuilder

如何创建一个可以通过访问 MaterialApp 上下文来监听所有页面的 BlocListener?

如何在 Flutter App 中正确使用 BlocListener 和 BlocProvider