使用 copywith 时 Flutter Bloc 状态未更新
Posted
技术标签:
【中文标题】使用 copywith 时 Flutter Bloc 状态未更新【英文标题】:Flutter Bloc State not updating when using copywith 【发布时间】:2021-08-02 01:05:40 【问题描述】:大纲:
-
导航到个人资料页面
传入用户 ID 以从 firestore 获取文档
将检索到的数据传递给 state.copyWith(data:data)
产生一个成功的状态并呈现用户界面
我的问题是,当我使用 state.copyWith(data:data) 时,即使数据 100% 存在,状态也没有更新,因为我可以在控制台中打印它。
代码:
UI:
class ProfileView extends StatelessWidget
final String uid;
final UserRepository userRepository = UserRepository();
ProfileView(required this.uid);
@override
Widget build(BuildContext context)
// Init repository
return BlocProvider<ProfileBloc>(
create: (context) => ProfileBloc(
// Should be able to pull user repo from context
userRepository: UserRepository(),
isCurrentUser: UserRepository().isCurrentUser(uid))
..add(InitializeProfile(uid: uid)),
// Get User Doc
child: _profileView(context));
Widget _profileView(context)
return BlocListener<ProfileBloc, ProfileState>(
listener: (context, state)
if (state.imageSourceActionSheetIsVisible)
_showImageSourceActionSheet(context);
final loadingStatus = state.loadingStatus;
if (loadingStatus is LoadingFailed)
showSnackBar(context, state.loadingStatus.exception.toString());
if (loadingStatus is LoadingInProgress)
LoadingView();
if (loadingStatus is LoadingFailed)
LoadingFailedView(exception: loadingStatus.exception.toString());
,
child: Scaffold(
appBar: _appBar(),
body: _profilePage(),
bottomNavigationBar: bottomNavbar(context),
),
);
BLoC:
class ProfileBloc extends Bloc<ProfileEvent, ProfileState>
final bool isCurrentUser;
final UserRepository userRepository;
final _imagePicker = ImagePicker();
ProfileBloc(
required this.isCurrentUser,
required this.userRepository,
) : super(ProfileState(
isCurrentUser: isCurrentUser, loadingStatus: LoadingInProgress()));
@override
Stream<ProfileState> mapEventToState(
ProfileEvent event,
) async*
if (event is InitializeProfile)
yield* _initProfile(uid: event.uid);
Stream<ProfileState> _initProfile(required String uid) async*
// Loading View
yield state.copyWith(loadingStatus: LoadingInProgress());
// Fetch profile data
try
final snapshot = await userRepository.getUserDoc(uid);
if (snapshot.exists)
final data = snapshot.data();
print(data['fullName']);
yield state.copyWith(
fullName: data["fullName"].toString(),
);
print(state.fullName);
yield state.copyWith(loadingStatus: LoadingSuccess());
else
yield state.copyWith(
loadingStatus: LoadingFailed("Profile Data Not present :("));
catch (e)
print(e);
State:
part of 'profile_bloc.dart';
class ProfileState
final bool isCurrentUser;
final String? fullName;
final LoadingStatus loadingStatus;
bool imageSourceActionSheetIsVisible;
ProfileState(
required bool isCurrentUser,
this.fullName,
this.loadingStatus = const InitialLoadingStatus(),
imageSourceActionSheetIsVisible = false,
) : this.isCurrentUser = isCurrentUser,
this.imageSourceActionSheetIsVisible = imageSourceActionSheetIsVisible;
ProfileState copyWith(
bool? isCurrentUser,
String? fullName,
LoadingStatus? loadingStatus,
bool? imageSourceActionSheetIsVisible,
)
print('State $fullName');
print('State $this.fullName');
return ProfileState(
isCurrentUser: this.isCurrentUser,
fullName: fullName ?? this.fullName,
loadingStatus: loadingStatus ?? this.loadingStatus,
imageSourceActionSheetIsVisible: imageSourceActionSheetIsVisible ??
this.imageSourceActionSheetIsVisible,
);
新更新的代码实现 Bloc 构建器而不是建议的侦听器
Widget _profileView(context)
return BlocBuilder<ProfileBloc, ProfileState>(builder: (context, state)
final loadingStatus = state.loadingStatus;
if (loadingStatus is LoadingInProgress)
return LoadingView();
if (loadingStatus is LoadingFailed)
return LoadingFailedView(exception: loadingStatus.exception.toString());
if (loadingStatus is LoadingSuccess)
return Scaffold(
appBar: _appBar(),
body: _profilePage(),
bottomNavigationBar: bottomNavbar(context),
);
return LoadingView();
);
这段代码仍然停留在加载屏幕上,当我打印出各种状态时,它被注册为一个事件,但是在使用 copy with 后状态打印出 null
感谢您的帮助
【问题讨论】:
【参考方案1】:看起来您在这段代码中弄乱了概念。 您的 Bloc 逻辑很好,问题出在 UI 层。
我的观察:
BlocListener
不会更新您的 UI(它不是为它设计的,而是为单次操作而设计的,例如导航/对话框显示)。考虑改用BlocBuilder
。
如果您确实需要 BlocListener
来触发您的 UI 状态更改,请考虑将您的 Widget 转换为 StatefulWidget
并按照您的方式使用 setState
,如下所示:
class MyWidget extends StatefulWidget
...
class MyWidgetState extends State<MyWidget>
Widget _currentWidget; // use it on your view hierarchy, remember to initialize with default Widget!
...
@override
Widget build(BuildContext context)
// Init repository
return BlocProvider<ProfileBloc>(
create: (context) => ProfileBloc(
// Should be able to pull user repo from context
userRepository: UserRepository(),
isCurrentUser: UserRepository().isCurrentUser(uid))
..add(InitializeProfile(uid: uid)),
// Get User Doc
child: _profileView(context));
Widget _profileView(context)
return BlocListener<ProfileBloc, ProfileState>(
listener: (context, state)
if (state.imageSourceActionSheetIsVisible)
_showImageSourceActionSheet(context);
final loadingStatus = state.loadingStatus;
if (loadingStatus is LoadingFailed)
showSnackBar(context, state.loadingStatus.exception.toString());
if (loadingStatus is LoadingInProgress)
/// HERE'S THE CHANGE:
setState(()
_currentWidget = LoadingWidget();
);
if (loadingStatus is LoadingFailed)
/// HERE'S THE CHANGE:
setState(()
_currentWidget = LoadingFailedView(exception: loadingStatus.exception.toString());
);
,
child: Scaffold(
appBar: _appBar(),
body: _profilePage(),
bottomNavigationBar: bottomNavbar(context),
),
);
【讨论】:
我建议按照他的说法使用 BlocBuilder。 感谢您的回复,遗憾的是,简单地更改为 bloc builder 并没有解决问题 我接受了以下帖子的建议,解决了问题,感谢您的意见【参考方案2】:从 bloc 文档中,我认为这是我的错误所在。
当我们在私有 mapEventToState 处理程序中产生一个状态时,我们总是产生一个新状态而不是改变状态。这是因为每次我们 yield 时,bloc 都会将 state 与 nextState 进行比较,并且只有在两个 state 不相等时才会触发 state 更改(transition)。如果我们只是变异并产生相同的状态实例,那么 state == nextState 将评估为 true,并且不会发生状态更改。
【讨论】:
【参考方案3】:这是因为您需要添加一个运算符来比较您的 ProfileState 对象。实际上,您的事件映射器会生成 ProfileState 对象,但所有这些对象都被视为相等,因此 UI 不会更新。 一个好的做法是让你的 state 实现 Equatable,所以你只需要定义用来比较对象的 props。
【讨论】:
感谢您的回复!但是,当我打印出状态是什么时,即使直接使用 copy with 后它也会返回 null,这表明状态没有被更新,不是吗?再次非常感谢您的回复。我已经编辑了我的状态和事件类来扩展 equatable 但是它没有效果,即使在调用 copywith 之后状态仍然返回 null 我认为实际上至少有 2 个状态,ProfileStateInitial 和 ProfileStateSuccess(也许是 ProfileStateFailure)会更简单和更干净,而不是实际上使用状态的属性来检查......状态。 这对我有用,谢谢。看来这种方法更好。对于未来的未来,人们将每个可能的状态划分为自己的独立类,该类扩展基类,而不是修改基类并检查修改。谢谢以上是关于使用 copywith 时 Flutter Bloc 状态未更新的主要内容,如果未能解决你的问题,请参考以下文章