BLoC 产生新状态后 BlocBuilder() 未更新

Posted

技术标签:

【中文标题】BLoC 产生新状态后 BlocBuilder() 未更新【英文标题】:BlocBuilder() not being updated after BLoC yielding new state 【发布时间】:2020-07-20 01:46:12 【问题描述】:

我是 Flutter 的 BLoC 模式的新手,我正在尝试使用它重建一个凌乱的 Flutter 应用程序。目前,我打算获取用户应用程序列表并使用 ListView.builder() 显示它们。问题是每当我的 AppsBloc 的状态发生变化时,我的 StatelessWidget 不会更新以显示新状态。我试过了:

使用 main.dart 中的 MultiBlocProvider() 而不是将此 appsBloc 嵌套在包含整个应用程序的 themeBloc 中 返回列表而不是地图,即使我的 aux 方法返回正确的地图 使用 StatefulWidget,仅在 ListView 上使用 BlocProvider()...

我一直在阅读类似项目中有关此问题的信息,问题可能出在 Equatable 上。但是,我无法确定任何错误,因为我也是使用 Equatable 的新手。我一直在 VScode 上用 yield* 行上的断点调试项目,似乎还可以。尽管如此,小部件并没有被重建:它一直显示与 InitialState 相对应的文本。

此外,即使所有状态都有一个被覆盖的 toString(),BLoC 也不会在控制台上打印任何内容

这是我的 3 个 BLoC 文件:

apps_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:device_apps/device_apps.dart';
import 'package:equatable/equatable.dart';

part 'apps_event.dart';
part 'apps_state.dart';

class AppsBloc extends Bloc<AppsEvent, AppsState> 
  @override
  AppsState get initialState => AppsInitial();

  @override
  Stream<AppsState> mapEventToState(AppsEvent event) async* 
    yield AppsLoadInProgress();
    if (event is AppsLoadRequest) 
      yield* _mapAppsLoadSuccessToState();
      
  

  Stream<AppsState> _mapAppsLoadSuccessToState() async* 
    try 
      final allApps = await DeviceApps.getInstalledApplications(
          onlyAppsWithLaunchIntent: true, includeSystemApps: true);

      final listaApps = allApps
        ..sort((a, b) =>
            a.appName.toLowerCase().compareTo(b.appName.toLowerCase()));

      final Map<Application, bool> res =
          Map.fromIterable(listaApps, value: (e) => false);

      yield AppsLoadSuccess(res);
     catch (_) 
      yield AppsLoadFailure();
    
  

apps_event.dart

part of 'apps_bloc.dart';

abstract class AppsEvent extends Equatable 
  const AppsEvent();

  @override
  List<Object> get props => [];


class AppsLoadRequest extends AppsEvent 

apps_state.dart

part of 'apps_bloc.dart';

abstract class AppsState extends Equatable 
  const AppsState();

  @override
  List<Object> get props => [];


class AppsInitial extends AppsState 
  @override
  String toString() => "State: AppInitial";


class AppsLoadInProgress extends AppsState 
  @override
  String toString() => "State: AppLoadInProgress";


class AppsLoadSuccess extends AppsState 
  final Map<Application, bool> allApps;
  const AppsLoadSuccess(this.allApps);

  @override
  List<Object> get props => [allApps];

  @override
  String toString() => "State: AppLoadSuccess, $allApps.length entries";


class AppsLoadFailure extends AppsState 
  @override
  String toString() => "State: AppLoadFailure";


main_screen.dart

class MainScreen extends StatelessWidget 
  const MainScreen(Key key) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return TabBarView(
        children: <Widget>[
          HomeScreen(),
          BlocProvider(
            create: (BuildContext context) => AppsBloc(),
            child: AppsScreen(),
          )
          ,
        ],
      );
  

apps_screen.dart

class AppsScreen extends StatelessWidget 
  const AppsScreen(Key key) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
          body: Container(
          margin: EdgeInsets.fromLTRB(30, 5, 10, 0),
          child: Column(children: <Widget>[
            Row(
              children: <Widget>[
                Text("Apps"),
              ],
            ),
            Row(children: <Widget>[
              Container(
                width: MediaQuery.of(context).size.width - 50,
                height: MediaQuery.of(context).size.height - 150,
                child: BlocBuilder<AppsBloc, AppsState>(
                  builder: (BuildContext context, AppsState state) 
                    if (state is AppsLoadSuccess)
                      return Text("LOADED");
                    else if (state is AppsInitial)
                      return GestureDetector(
                          onTap: () => AppsBloc().add(AppsLoadRequest()),
                          child: Text("INITIAL"));
                    else if (state is AppsLoadInProgress)
                      return Text("LOADING...");
                    else if (state is AppsLoadFailure)
                      return Text("LOADING FAILED");
                  ,
                ),
              ),
            ])
          ])),
    );
  

【问题讨论】:

【参考方案1】:

GestureDetector.onTap() 中创建一个新的AppsBloc(),这是错误的。所以,你需要:

apps_screen.dart:

  AppsBloc _appsBloc;

  @override
  void initState() 
    super.initState();

    _appsBloc = BlocProvider.of<AppsBloc>(context);
  

//...

  @override
  Widget build(BuildContext context) 
    //...
    return GestureDetector(
      onTap: () => _appsBloc.add(AppsLoadRequest()),
      child: Text("INITIAL")
    );
    //...
  

或者,即使没有 _appsBloc 字段,您也可以这样做:

BlocProvider.of<AppsBloc>(context).add(AppsLoadRequest())

【讨论】:

这解决了我的问题,非常感谢!我之前也尝试过,但看起来我错过了一些重要的东西,现在它可以工作了。干杯。

以上是关于BLoC 产生新状态后 BlocBuilder() 未更新的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Bloc:更新后未调用 BlocBuilder,ListView 仍显示旧数据

使用 blocbuilder 检查 bloc 状态

Flutter bloc:更新状态不会重新调用 BlocBuilder

BlocBuilder 没有获取新状态中的值。查看更新颤振

Flutter bloc 在 7.2.0 版本中没有用 Equatable 重建

flutter_bloc 的 BlocBuilder 能否避免重建 Widget 的未更改部分?