在BLoC中触发初始事件

34

示例状态:

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

class LoadingState extends ExampleState {
  //
}

class LoadedState extends ExampleState {
  //
}

class FailedState extends ExampleState {
  //
}

示例事件:

abstract class ExampleEvent extends Equatable {
  //
}

class SubscribeEvent extends ExampleEvent {
  //
}

class UnsubscribeEvent extends ExampleEvent {
  //
}

class FetchEvent extends ExampleEvent {
  // 
}

示例区块:

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();
      }
    }
  }
}

示例屏幕:

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();
}
3个回答

71

您的不适确实有原因 - 不应该从build()方法中触发事件(Flutter框架可能需要多次触发build())

我们的情况是在Bloc创建时触发初始事件

可能性概述

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

create: 回调仅在BlocProvider挂载时触发一次,并且BlocProvider会在BlocProvider解除挂载时关闭() 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(); 
...

如果你有任何问题,欢迎在评论中联系我


1
非常感谢,这些确实帮了很多。 - vendrick
感谢您清楚地解释。从您的回答中我得到了更清晰的图像。 - Eaweb
看起来第三种情况正是我正在寻找的。谢谢你! - Vladislav Kulikov
2
当我尝试第二种情况时,BlocListener不起作用。 - OguzKaanAkyalcin
在最新版本的bloc包中,使用create: (context) => ExampleBloc()..add(Fetch())将会抛出错误。'package:flutter/src/widgets/framework.dart': Failed assertion: line 6151 pos 12: 'renderObject.child == child': is not true. - MUHAMMAD SHAHID RAFI C P
显示剩余2条评论

10

Sergey Salnikov的回答很好。不过,我认为还可以提出另一个建议。

在我的main.dart文件中,我使用MultiBlocProvider创建所有的bloc,以便在下层组件中进一步使用,如下所示:

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 provider并添加一个事件。

class _ExampleScreenState extends State<ExampleScreen> {
    

  @override
  void initState() {
    super.initState();
    BlocProvider.of<OneBloc>(context)
        .add(FetchData);
  }

这是因为该块已经从根widget中提供了。


我已经尝试过这个,但它仍然显示“如果您使用的上下文来自BlocProvider上面的小部件,则可能会发生这种情况”。 - OguzKaanAkyalcin
@OguzKaanAkyalcin请确保BlocProvider在应用程序的最顶部。 - flutterisbae

6
简单来说:
1. 使用 BlocProvider,在创建期间调用它。
   BlocProvider(create: (context) => ExampleBloc()..add(Fetch()))

2. 使用 BlocState,将其用作

   class _ExampleScreenStateState extends State<ExampleScreenState> {
      ExampleBloc _exampleBloc;

      @override
     void initState() {
     super.initState();
     _exampleBloc = ExampleBloc()..add(Fetch());
   }

   @override
   void dispose() {
     super.dispose();
     _exampleBloc.close();
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接