【问题标题】:Bloc event triggered only onceBloc 事件仅触发一次
【发布时间】:2020-12-11 14:01:30
【问题描述】:

我关注this approach 来管理区块并保存一些代码。

我想从我的 API 中获取数据。我想通过initState 方法并调用myBloc.add(MyEvent())。但问题是,它只被调用过一次。

我已经四处搜索并在几个博客上尝试了一些解决方案,这是官方 Github 回购问题,但仍然无法正常工作。我找到了similar question,但由于我没有使用任何依赖注入或单例,我无法找到问题的确切内容和位置,我的问题仍未解决。

这是我迄今为止尝试过的,但仍然没有解决问题:

  1. 从集团中删除 Equatable
  2. 硬重新加载设备
  3. 运行flutter clean 命令
  4. 重新运行应用程序

为了清楚起见,请看一下这段录音。

最后,我的脚本如下所示:

leave_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:flutter_prismahr/app/data/models/leave_model.dart';
import 'package:flutter_prismahr/app/data/repositories/leave_repository.dart';
import 'package:meta/meta.dart';

part 'leave_event.dart';
part 'leave_state.dart';

class LeaveBloc extends Bloc<LeaveEvent, LeaveState> {
  LeaveBloc() : super(LeaveInitial());

  final LeaveRepository repository = LeaveRepository();

  @override
  Stream<LeaveState> mapEventToState(
    LeaveEvent event,
  ) async* {
    print('TRIGGERED EVENT IS: $event');
    if (event is LeaveScreenInitialized) {
      yield LeaveLoading();

      try {
        final response = await repository.fetch();
        if (response is List<Leave> && response.isNotEmpty) {
          yield LeaveLoaded(data: response);
        } else {
          yield LeaveEmpty();
        }
      } catch (e) {
        yield LeaveFailure(error: e.toString());
      }
    }

    if (event is LeaveAdded) {
      yield LeaveCreated(data: event.data);
    }
  }
}

leave_event.dart

part of 'leave_bloc.dart';

abstract class LeaveEvent {
  const LeaveEvent();

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

class LeaveScreenInitialized extends LeaveEvent {}

class LeaveAdded extends LeaveEvent {
  final Leave data;
  const LeaveAdded({@required this.data}) : assert(data != null);

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

  @override
  String toString() => 'LeaveAdded { data: $data }';
}

leave_state.dart

part of 'leave_bloc.dart';

abstract class LeaveState {
  const LeaveState();

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

class LeaveInitial extends LeaveState {}

class LeaveLoading extends LeaveState {}

class LeaveEmpty extends LeaveState {}

class LeaveLoaded extends LeaveState {
  final List<Leave> data;
  const LeaveLoaded({@required this.data}) : assert(data != null);

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

  @override
  String toString() => 'LeaveLoaded { data: $data }';
}

class LeaveFailure extends LeaveState {
  final String error;
  const LeaveFailure({@required this.error}) : assert(error != null);

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

  @override
  String toString() => 'LeaveFailure { error: $error }';
}

class LeaveCreated extends LeaveState {
  final Leave data;
  const LeaveCreated({@required this.data}) : assert(data != null);

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

  @override
  String toString() => 'LeaveCreated { data: $data }';
}

leave_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_prismahr/app/bloc/leave/leave_bloc.dart';
import 'package:flutter_prismahr/app/bloc/leave_update/leave_update_bloc.dart';
import 'package:flutter_prismahr/app/components/empty.dart';
import 'package:flutter_prismahr/app/data/models/leave_model.dart';
import 'package:flutter_prismahr/app/routes/routes.dart';

import 'components/leave_list.dart';
import 'components/leave_list_loading.dart';

class LeaveScreen extends StatefulWidget {
  LeaveScreen({Key key}) : super(key: key);

  @override
  _LeaveScreenState createState() => _LeaveScreenState();
}

class _LeaveScreenState extends State<LeaveScreen> {
  LeaveBloc _leaveBloc;
  LeaveUpdateBloc _leaveUpdateBloc;
  List<Leave> _leaves;

  @override
  void initState() {
    print('INIT STATE CALLED');
    _leaves = <Leave>[];
    _leaveBloc = BlocProvider.of<LeaveBloc>(context);
    _leaveUpdateBloc = BlocProvider.of<LeaveUpdateBloc>(context);
    _leaveBloc.add(LeaveScreenInitialized());
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            pinned: true,
            floating: true,
            title: Text(
              'Leave Requests',
              style: Theme.of(context)
                  .textTheme
                  .headline6
                  .copyWith(fontWeight: FontWeight.w900),
            ),
          ),
          SliverToBoxAdapter(
            child: MultiBlocListener(
              listeners: [
                BlocListener<LeaveBloc, LeaveState>(
                  listener: (context, state) {
                    if (state is LeaveLoaded) {
                      setState(() {
                        _leaves = state.data;
                      });
                    }

                    if (state is LeaveCreated) {
                      setState(() {
                        _leaves.add(state.data);
                      });
                    }
                  },
                ),
                BlocListener<LeaveUpdateBloc, LeaveUpdateState>(
                  listener: (context, state) {
                    if (state is LeaveUpdateSuccess) {
                      int index = _leaves.indexWhere((leave) {
                        return leave.id == state.data.id;
                      });

                      setState(() {
                        _leaves[index] = state.data;
                        _leaveUpdateBloc.add(ResetState());
                      });
                    }
                  },
                ),
              ],
              child: BlocBuilder<LeaveBloc, LeaveState>(
                builder: (context, state) {
                  if (state is LeaveLoading) {
                    return Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 20,
                        vertical: 30,
                      ),
                      child: LeaveListLoading(),
                    );
                  }

                  if (state is LeaveEmpty) {
                    return Padding(
                      padding: const EdgeInsets.only(top: 100),
                      child: Empty(),
                    );
                  }

                  return LeaveList(
                    data: _leaves,
                    bloc: _leaveUpdateBloc,
                  );
                },
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: BlocBuilder<LeaveBloc, LeaveState>(
        builder: (context, state) {
          if (state is! LeaveLoading) {
            return FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () async {
                final data = await Navigator.of(context).pushNamed(
                  Routes.LEAVE_CREATE,
                );

                if (data != null) {
                  _leaveBloc.add(LeaveAdded(data: data));
                }
              },
            );
          }
          return SizedBox();
        },
      ),
    );
  }
}

app_router.dart

...
...

import 'package:flutter_prismahr/app/views/leave/leave_screen.dart';

...
...

class Router {
  // Provide a function to handle named routes. Use this function to
  // identify the named route being pushed, and create the correct
  // screen.
  final LeaveBloc _leaveBloc = LeaveBloc();
  final LeaveUpdateBloc _leaveUpdateBloc = LeaveUpdateBloc();
  final LeaveCreateBloc _leaveCreateBloc = LeaveCreateBloc();

  Route<dynamic> generateRoute(RouteSettings settings) {
    final RouteArguments args = settings.arguments;

    switch (settings.name) {
      ...
      ...

      case Routes.LEAVE:
        return MaterialPageRoute(
          builder: (_) => MultiBlocProvider(
            providers: [
              BlocProvider(create: (context) => _leaveBloc),
              BlocProvider(create: (context) => _leaveUpdateBloc),
              BlocProvider(create: (context) => _leaveCreateBloc),
            ],
            child: LeaveScreen(),
          ),
        );

      ...
      ...

      default:
        return MaterialPageRoute(
          builder: (_) => Scaffold(
            body: Center(
              child: Text('No route defined for ${settings.name}'),
            ),
          ),
        );
    }
  }

  void dispose() {
    _leaveBloc.close();
    _leaveUpdateBloc.close();
    _leaveCreateBloc.close();
  }
}

有什么线索吗??

【问题讨论】:

  • 尝试对状态和事件使用 equatable 包
  • 我做了,你可以看到它被注释掉以测试差异。有或没有等价表都不起作用
  • 你解决了吗?我面临完全相同的问题。
  • @zeromaro 我发布了这个问题的答案,请看一下。希望它能帮助你和其他有同样问题的人

标签: flutter bloc flutter-bloc


【解决方案1】:

TL;DR

  1. 不要在路由级块访问中使用BlocProvider(create: (context) =&gt; _yourBloc)
  2. 请改用BlocProvider.value(value: _leaveBloc, child: LeaveScreen())
  3. 不要关闭 UI 的 dispose 方法内的块。

在@Rolly 的帮助下,我们设法解决了这个问题的主要原因。发生这种情况是因为在这种情况下,我使用路由级别 bloc 访问提供程序并从 UI 上的 dispose 方法关闭 bloc。由于这是很久以前的问题,我不确定我是否记得所有内容。但我会尽量解释得通俗易懂,并尽我所能。

主要原因

将集团视为一扇门。当用户访问它时,我告诉我的应用程序在LeaveScreen 上进入leaveBloc,搞得一团糟,然后给他们他们需要的东西。第一次访问它时,它可以工作,因为它是打开的,应用程序知道要做什么,直到用户按下返回按钮并且我的脚本将其关闭。

当用户返回该页面时,集团已关闭,应用程序试图敲门但它不会打开,所以它不知道该怎么做,而是站在那里等待开门打开了。

解决方案

因为我们使用的是路由级别的块访问,所以应用程序不应关闭 UI 级别内的任何块。相反,在路由文件上创建一个 dispose 函数并在您的小部件树顶部附近触发它。像这样:

router.dart

class Router {
  final LeaveBloc _leaveBloc = LeaveBloc(
    ...
  );

  Route<dynamic> generateRoute(RouteSettings settings) {
    case Routes.LEAVE:
        return MaterialPageRoute(
            builder: (_) =>
                BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));
  }

  void dispose() {
    _leaveBloc.close();
    // another bloc
    // another bloc
  }
}

main.dart

void main() async {
  ...

  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final Router _router = Router();
  ...


  @override
  void dispose() {
    _router.dispose(); // <-- trigger dispose when the user closes the app
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...
    );
  }
}

附加说明

请注意,我正在更改 route 脚本:

    case Routes.LEAVE:
        return MaterialPageRoute(
          builder: (_) => MultiBlocProvider(
            providers: [
              BlocProvider(create: (context) => _leaveBloc),
              BlocProvider(create: (context) => _leaveUpdateBloc),
              BlocProvider(create: (context) => _leaveCreateBloc),
            ],
            child: LeaveScreen(),
          ),
        );

到这里:

    case Routes.LEAVE:
        return MaterialPageRoute(
            builder: (_) =>
                BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));

这是必要的,因为我们需要告诉应用我们不想从 UI 级别关闭块并让路由器的 dispose 方法完成这项工作。这行的 bloc 库的文档中有正式的解释。

我们在向路由提供 CounterBloc 实例时使用 BlocProvider.value,因为我们不希望 BlocProvider 处理 bloc 的处理(因为 _AppState 负责处理)。

参考: bloc library's official documentation (Bloc Access - Named Route Access)

【讨论】:

  • 你是我今天剩下的英雄 :D 谢谢!
  • 很高兴它对你有用,祝你有美好的一天!
【解决方案2】:

initState 中只调用一次是正常的。该生命周期挂钩仅在创建小部件时执行一次。

如果您希望每次导航到详细信息屏幕时都执行它,请导航到新的详细信息屏幕,因为这将每次重新创建您的详细信息小部件并添加您的事件。

【讨论】:

  • 对不起,我不明白.. 我确实导航到不同的屏幕。它也得到了重建。但是 add 事件不会触发任何东西。它甚至不打印我放在mapEventToState 方法中的打印语句。
猜你喜欢
  • 2021-11-24
  • 2022-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多