Bloc 事件只触发一次
Bloc event triggered only once
我按照this approach来管理区块并节省了一些代码行。
我想从我的 API 中获取数据。我想通过 initState
方法完成并调用 myBloc.add(MyEvent())
。但问题是,它只调用了一次。
我用谷歌搜索并在几个博客上尝试了一些解决方案,这是官方的 Github 回购问题,但仍然无效。我找到了一个 但由于我没有使用任何依赖注入或单例,我无法找到问题的确切位置和位置,我的问题仍然没有解决。
这是我到目前为止已经尝试过但仍未解决问题的方法:
- 从集团中删除 Equatable
- 硬重新加载设备
- 运行
flutter clean
命令
- 重新 运行 应用程序
为了清楚起见,请看一下这段录音。
最后,这是我的脚本的样子:
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();
}
}
有线索吗??
initState
只调用一次是正常的。该生命周期挂钩仅在创建小部件时执行一次。
如果您希望每次导航到详细信息屏幕时都执行它,请导航到新的详细信息屏幕,因为这样每次都会 re-create 您的详细信息小部件并添加您的事件。
TL;DR
- Do not use
BlocProvider(create: (context) => _yourBloc)
on route-level bloc access.
- Use
BlocProvider.value(value: _leaveBloc, child: LeaveScreen())
instead.
- Do not close the bloc inside UI's
dispose
method.
在@Rolly 的帮助下,我们已经设法解决了这个问题的主要原因。发生这种情况是因为在这种情况下,我正在使用路由级 bloc 访问提供程序并从 UI 上的 dispose
方法关闭 bloc。由于这是很久以前的问题,我不确定我是否记得所有内容。但我会尽量解释得通,而且我记得。
主要原因
把集团想象成一扇门。当用户访问它时,我告诉我的应用程序在 LeaveScreen
上进入 leaveBloc
,搞得一团糟,然后给他们他们需要的东西。第一次访问它时,它可以工作,因为它是打开的,应用程序知道该做什么,直到用户按下后退按钮并且我的脚本将其关闭。
当用户返回那个页面时,bloc 已经关闭,app 尝试敲门但打不开,所以它不知道该怎么办,只能站在那里等门即将开放。
解决方案
因为我们正在使用 route-level 区块访问权限,所以应用程序不应关闭 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 级别关闭 bloc,让路由器的 dispose 方法完成这项工作。这行在 bloc 库的文档中有正式解释。
We're using BlocProvider.value when providing the CounterBloc instance to the routes because we don't want the BlocProvider to handle disposing the bloc (since _AppState is responsible for that).
参考:
bloc library's official documentation (Bloc Access - Named Route Access)
我按照this approach来管理区块并节省了一些代码行。
我想从我的 API 中获取数据。我想通过 initState
方法完成并调用 myBloc.add(MyEvent())
。但问题是,它只调用了一次。
我用谷歌搜索并在几个博客上尝试了一些解决方案,这是官方的 Github 回购问题,但仍然无效。我找到了一个
这是我到目前为止已经尝试过但仍未解决问题的方法:
- 从集团中删除 Equatable
- 硬重新加载设备
- 运行
flutter clean
命令 - 重新 运行 应用程序
为了清楚起见,请看一下这段录音。
最后,这是我的脚本的样子:
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();
}
}
有线索吗??
initState
只调用一次是正常的。该生命周期挂钩仅在创建小部件时执行一次。
如果您希望每次导航到详细信息屏幕时都执行它,请导航到新的详细信息屏幕,因为这样每次都会 re-create 您的详细信息小部件并添加您的事件。
TL;DR
- Do not use
BlocProvider(create: (context) => _yourBloc)
on route-level bloc access.- Use
BlocProvider.value(value: _leaveBloc, child: LeaveScreen())
instead.- Do not close the bloc inside UI's
dispose
method.
在@Rolly 的帮助下,我们已经设法解决了这个问题的主要原因。发生这种情况是因为在这种情况下,我正在使用路由级 bloc 访问提供程序并从 UI 上的 dispose
方法关闭 bloc。由于这是很久以前的问题,我不确定我是否记得所有内容。但我会尽量解释得通,而且我记得。
主要原因
把集团想象成一扇门。当用户访问它时,我告诉我的应用程序在 LeaveScreen
上进入 leaveBloc
,搞得一团糟,然后给他们他们需要的东西。第一次访问它时,它可以工作,因为它是打开的,应用程序知道该做什么,直到用户按下后退按钮并且我的脚本将其关闭。
当用户返回那个页面时,bloc 已经关闭,app 尝试敲门但打不开,所以它不知道该怎么办,只能站在那里等门即将开放。
解决方案
因为我们正在使用 route-level 区块访问权限,所以应用程序不应关闭 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 级别关闭 bloc,让路由器的 dispose 方法完成这项工作。这行在 bloc 库的文档中有正式解释。
We're using BlocProvider.value when providing the CounterBloc instance to the routes because we don't want the BlocProvider to handle disposing the bloc (since _AppState is responsible for that).
参考: bloc library's official documentation (Bloc Access - Named Route Access)