【问题标题】:Triggering initial event in BLoC在 BLoC 中触发初始事件
【发布时间】:2020-10-20 04:55:33
【问题描述】:

example_states:

abstract class ExampleState extends Equatable {
  const ExampleState();
}

class LoadingState extends ExampleState {
  //
}

class LoadedState extends ExampleState {
  //
}

class FailedState extends ExampleState {
  //
}

example_events:

abstract class ExampleEvent extends Equatable {
  //
}

class SubscribeEvent extends ExampleEvent {
  //
}

class UnsubscribeEvent extends ExampleEvent {
  //
}

class FetchEvent extends ExampleEvent {
  // 
}

example_bloc:

class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
  @override
  ExampleState get initialState => LoadingState();

  @override
  Stream<ExampleState> mapEventToState(
    ExampleEvent event,
  ) async* {
    if (event is SubscribeEvent) {
      //
    } else if (event is UnsubscribeEvent) {
      //
    } else if (event is FetchEvent) {
      yield LoadingState();
      try {
        // network calls
        yield LoadedState();
      } catch (_) {
        yield FailedState();
      }
    }
  }
}

example_screen:

class ExampleScreenState extends StatelessWidget {
  // ignore: close_sinks
  final blocA = ExampleBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocBuilder<ExampleBloc, ExampleState>(
        bloc: blocA,
        // ignore: missing_return
        builder: (BuildContext context, state) {
          if (state is LoadingState) {
            blocA.add(Fetch());
            return CircularProgressBar();
          }

          if (state is LoadedState) {
            //...
          }

          if (state is FailedState) {
            //...
          }
        },
      ),
    );
  }
}

正如您在 example_bloc 中看到的,初始状态是 LoadingState(),在构建中它显示圆形进度条。我使用 Fetch() 事件来触发下一个状态。但是我在那里使用它感觉不舒服。我想做的是:

当应用程序启动时,它应该显示 LoadingState 并开始网络调用,然后当它全部完成时,它应该显示 LoadedState 和网络调用结果,如果出现问题,它应该显示 FailedState。我想不做就实现这些

if (state is LoadingState) {
  blocA.add(Fetch());
  return CircularProgressBar();
}

【问题讨论】:

    标签: flutter dart bloc flutter-bloc


    【解决方案1】:

    您的不适确实是有原因的 - 不应从 build() 方法中触发任何事件(build() 可以根据 Flutter 框架的需要多次触发)

    我们的案例是在创建 Bloc 时触发初始事件

    可能性概览

    1. 使用 BlocProvider 插入 Bloc 的情况 - 这是首选方式

    create: 回调仅在 BlocProvider 挂载时触发一次,BlocProvider 将在 BlocProvider 卸载时 close() bloc

        class ExampleScreenState extends StatelessWidget {
        
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              body: BlocProvider(
                create: (context) => ExampleBloc()..add(Fetch()), // <-- first event, 
                child: BlocBuilder<ExampleBloc, ExampleState>(
                  builder: (BuildContext context, state) {
                    ...
                  },
                ),
              ),
            );
          }
        }
    
    1. 在 Statefull 小部件的State 中创建 Bloc 时的情况
    class _ExampleScreenStateState extends State<ExampleScreenState> {
      ExampleBloc _exampleBloc;
    
      @override
      void initState() {
        super.initState();
        _exampleBloc = ExampleBloc();
        _exampleBloc.add(Fetch());
        // or use cascade notation
        // _exampleBloc = ExampleBloc()..add(Fetch());
      }
    
      @override
      void dispose() {
        super.dispose();
        _exampleBloc.close(); // do not forget to close, prefer use BlocProvider - it would handle it for you
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: BlocBuilder<ExampleBloc, ExampleState>(
            bloc: _exampleBloc,
            builder: (BuildContext context, state) {
             ...
            },
          ),
        );
      }
    }
    
    1. 在创建 Bloc 实例时添加第一个事件 - 这种方式在测试时有缺点,因为第一个事件是隐式的
    class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
    
      ...
    
      ExampleBloc() {
        add(Fetch());
      }
    }
    
    // insert it to widget tree with BlocProvider or create in State
    BlocProvider( create: (_) => ExampleBloc(), ...
    
    // or in State
    
    class _ExampleScreenStateState extends State<ExampleScreenState> {
      final _exampleBloc = ExampleBloc(); 
    ...
    

    请随时在 cmets 与我联系

    【讨论】:

    • 非常感谢,这些真的很有帮助
    • 当我在 BlocProvider 中做ExampleBloc()..add(Fetch() 时,会导致页面的单元测试失败,A Timer is still pending even after the widget tree was disposed.,如何解决?
    • 抱歉,我不知道 - 因为我们谈论的是 Bloc 的第一个活动。使用测试代码 sn-p 和您测试的 Widget 代码创建新问题
    • 感谢您解释清楚。从你的回答中得到了更清晰的画面。
    • 当我尝试了第二种情况时 BlocListener 不起作用。
    【解决方案2】:

    Sergey Salnikov 有一个很好的答案。不过,我想我可以添加另一个建议。

    在我的 main.dart 文件中,我使用 MultiBlocProvider 来创建我所有的块,以便在树的下方使用。像这样

    Widget build(BuildContext context) {
        return MultiBlocProvider(
          providers: <BlocProvider<dynamic>>[
            BlocProvider<OneBloc>(create: (_) => OneBloc()),
            BlocProvider<TwoBloc>(create: (_) => TwoBloc()),
          ],
          child: MaterialApp( // Rest of your app )
    

    然后,当我需要在加载页面时调用事件时,在这种情况下,我想根据所选的列表图块获取一些数据,并且我需要的选项比 FutureBuilder 所能提供的更多,我简单地使用了 initState() ;并调用了 bloc 提供者并添加了一个事件。

    class _ExampleScreenState extends State<ExampleScreen> {
        
    
      @override
      void initState() {
        super.initState();
        BlocProvider.of<OneBloc>(context)
            .add(FetchData);
      }
    

    之所以有效,是因为该块已从根小部件提供。

    【讨论】:

    • 我已经尝试过了,但它仍然显示“如果您使用的上下文来自 BlocProvider 上方的小部件,则会发生这种情况”。
    • @OguzKaanAkyalcin 确保 BlocProvider 位于应用程序的最顶部。
    猜你喜欢
    • 2022-07-26
    • 2022-01-01
    • 2020-12-11
    • 2021-11-24
    • 2013-05-20
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    相关资源
    最近更新 更多