BLoC mapEventToState() 未触发
BLoC mapEventToState() not triggered
这是我的 ProfileBloc
处理我的个人资料屏幕的状态。在 Bloc 中,在用户数据成功更新后,我发出 ProfileScreenSelected()
事件,以便我返回到预填充屏幕(对应于 EditingUserInfo
状态),但由于某种原因事件不会触发 mapeventtostate()
方法,我保持成功状态。我不明白有什么原因会导致 mapeventtostate 方法不触发。
ProfileEvent
part of 'profile_bloc.dart';
abstract class ProfileEvent extends Equatable {
const ProfileEvent();
@override
List<Object> get props => [];
}
class ProfileScreenSelected extends ProfileEvent {
}
class SaveButtonTapped extends ProfileEvent {
}
class UsernameChanged extends ProfileEvent {
final String value;
UsernameChanged({this.value});
@override
List<Object> get props => [value];
}
class BioChanged extends ProfileEvent {
final String value;
BioChanged({this.value});
@override
List<Object> get props => [value];
}
class CityChanged extends ProfileEvent {
final String value;
CityChanged({this.value});
@override
List<Object> get props => [value];
}
ProfileState
part of 'profile_bloc.dart';
abstract class ProfileState extends Equatable {
const ProfileState();
@override
List<Object> get props => [];
}
class ProfileInitial extends ProfileState {}
class EditingUserInfo extends ProfileState {
final Username username;
final Bio bio;
final PhotoUrl photoUrl;
final City city;
final FormzStatus status;
const EditingUserInfo({
this.username = const Username.pure(),
this.bio = const Bio.pure(),
this.photoUrl = const PhotoUrl.pure(),
this.city = const City.pure(),
this.status = FormzStatus.pure,
});
EditingUserInfo copyWith({Username username, Bio bio, PhotoUrl photoUrl, City city, FormzStatus status}){
return EditingUserInfo(
username: username ?? this.username,
bio: bio ?? this.bio,
photoUrl: photoUrl ?? this.photoUrl,
city: city ?? this.city,
status: status ?? this.status,
);
}
@override
List<Object> get props => [username, bio, photoUrl, city, status];
}
class Loading extends ProfileState {}
class Error extends ProfileState {
final String message;
const Error({this.message});
@override
List<Object> get props => [message];
}
class Success extends ProfileState {
final String message;
const Success({this.message});
@override
List<Object> get props => [message];
}
ProfileBloc
import 'dart:async';
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_native_image/flutter_native_image.dart';
import 'package:formz/formz.dart';
import 'package:meta/meta.dart';
import 'package:muslim_coloc/blocs/authentication/authentication_bloc.dart';
import 'package:muslim_coloc/blocs/profile/cubit/profile_picture_cubit.dart';
import 'package:muslim_coloc/blocs/profile/models/city.dart';
import 'package:muslim_coloc/blocs/profile/models/photoUrl.dart';
import 'package:muslim_coloc/blocs/profile/models/username.dart';
import 'package:muslim_coloc/blocs/profile/models/bio.dart';
import 'package:muslim_coloc/models/user.dart';
import 'package:muslim_coloc/repositories/firebase_user_repository.dart';
import 'package:firebase_storage/firebase_storage.dart';
part 'profile_event.dart';
part 'profile_state.dart';
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
final AuthenticationBloc _authenticationBloc;
final ProfilePictureCubit _profilePictureCubit;
final FirebaseUserRepository _firebaseUserRepository;
StreamSubscription<AuthenticationState> _authSubscription;
ProfileBloc(
{@required authenticationBloc,
@required firebaseUserRepository,
@required profilePictureCubit})
: _authenticationBloc = authenticationBloc,
_profilePictureCubit = profilePictureCubit,
_firebaseUserRepository = firebaseUserRepository,
super(ProfileInitial()) {
_authSubscription = _authenticationBloc.listen((onData) {});
}
@override
Stream<ProfileState> mapEventToState(
ProfileEvent event,
) async* {
if (event is ProfileScreenSelected) {
//when screen is selected, fill in the state fields in order to pre-fill the UI
User user = _authenticationBloc.state.user;
yield EditingUserInfo(
username: Username.dirty(user.username),
bio: (user.bio != null) ? Bio.dirty(user.bio) : Bio.pure(),
photoUrl: (user.photoUrl != null)
? PhotoUrl.dirty(user.photoUrl)
: PhotoUrl.pure(),
city: (user.city != null) ? City.dirty(user.city) : City.pure(),
status: Formz.validate([
Username.dirty(user.username),
(user.bio != null) ? Bio.dirty(user.bio) : Bio.pure(),
(user.photoUrl != null)
? PhotoUrl.dirty(user.photoUrl)
: PhotoUrl.pure(),
(user.city != null) ? City.dirty(user.city) : City.pure(),
]));
} else if (event is UsernameChanged) {
final newUsernameValue = Username.dirty(event.value);
yield (state as EditingUserInfo).copyWith(
username: newUsernameValue,
status: Formz.validate([
newUsernameValue,
(state as EditingUserInfo).bio,
(state as EditingUserInfo).photoUrl,
(state as EditingUserInfo).city
]),
);
} else if (event is BioChanged) {
final newBioValue = Bio.dirty(event.value);
yield (state as EditingUserInfo).copyWith(
bio: newBioValue,
status: Formz.validate([
newBioValue,
(state as EditingUserInfo).username,
(state as EditingUserInfo).photoUrl,
(state as EditingUserInfo).city
]));
} else if (event is CityChanged) {
final newCityValue = City.dirty(event.value);
yield (state as EditingUserInfo).copyWith(
city: newCityValue,
status: Formz.validate([
newCityValue,
(state as EditingUserInfo).username,
(state as EditingUserInfo).photoUrl,
(state as EditingUserInfo).bio
]));
} else if (event is SaveButtonTapped) {
// when save button is tapped, update the authenticated user (bloc) with new info from the state
if (!(state as EditingUserInfo).status.isValidated) return;
User user = _authenticationBloc.state.user;
if (state is EditingUserInfo) {
User updatedUser = User(
id: user.id,
username: (state as EditingUserInfo).username.value,
bio: (state as EditingUserInfo).bio.value,
photoUrl: (state as EditingUserInfo).photoUrl.value,
city: (state as EditingUserInfo).city.value,
favorite: user.favorite,
);
yield Loading();
if (_profilePictureCubit.state.profilePicture != null) {
String photoUrl = await uploadFile(
_profilePictureCubit.state.profilePicture, user.id);
updatedUser = updatedUser.copyWith(photoUrl: photoUrl);
_profilePictureCubit.emptyState();
}
try {
await _firebaseUserRepository.updateUser(updatedUser);
_authenticationBloc.add(AuthenticationUserChanged(
updatedUser)); // Shouldn't go back to home page
/*
When I click the sumbit button and that I successfully sent the data to firebase, the autheticated user changes -as his data are not up to date anymore- and I wait until the updated data are received to launch the Success state
*/
await for (var state in _authenticationBloc) {
if (state.status == AuthenticationStatus.authenticated) {
yield Success(message: 'Le profil a bien été mis à jour');
this.add(ProfileScreenSelected()); // this event does not trigger mapEventToState
}
}
// here, back to editing user info
} on Exception catch (e) {
yield Error(message: e.toString());
// here, back to editing user info
}
print("is this code executed");
//yield ProfileInitial();
}
}
}
Future<String> uploadFile(File _image, String id) async {
Reference profilePhotoReference =
FirebaseStorage.instance.ref('profilePhoto/' + id);
ImageProperties properties =
await FlutterNativeImage.getImageProperties(_image.path);
File compressedFile = await FlutterNativeImage.compressImage(_image.path,
quality: 80,
targetWidth: 300,
targetHeight: (properties.height * 300 / properties.width).round());
profilePhotoReference.putFile(compressedFile);
String returnURL;
//.catchError((){}); //TODO
await profilePhotoReference.getDownloadURL().then((fileURL) {
returnURL = fileURL;
});
return returnURL;
}
}
我也尝试在布局中添加事件,但同样的事情,事件被考虑在内,但 mapEventToState 没有被触发
BlocListener<ProfileBloc, ProfileState>(
listenWhen: (previousState, state) => (state is Error || state is Success) ? true : false,
listener: (context, state) {
Scaffold.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text(
state is Error ? state.message : state is Success ? state.message : ''
),
));
context.read<ProfileBloc>().add(ProfileScreenSelected());
},),
在您的布局文件中,您应该调用这样的 bloc 事件:
profileBloc.add(ProfileScreenSelected());
如果你改变这一行
context.read<ProfileBloc>().add(ProfileScreenSelected());
至此
BlocProvider.of<ProfileBloc>(context).add(ProfileScreenSelected());
应该会有帮助
我刚刚解决了我的问题。对于那些可能面临这种情况的人来说,mapEventToState()
方法被阻止是因为那段代码中的 await for
await for (var state in _authenticationBloc) {
if (state.status == AuthenticationStatus.authenticated) {
yield Success(message: 'Le profil a bien été mis à jour');
this.add(ProfileScreenSelected()); // this event does not trigger mapEventToState
}
}
因此,为了在添加事件后跳出循环,我只是在之后添加了一个 break;
。然后 mapEventToState
方法终于可以触发了。
这是我的 ProfileBloc
处理我的个人资料屏幕的状态。在 Bloc 中,在用户数据成功更新后,我发出 ProfileScreenSelected()
事件,以便我返回到预填充屏幕(对应于 EditingUserInfo
状态),但由于某种原因事件不会触发 mapeventtostate()
方法,我保持成功状态。我不明白有什么原因会导致 mapeventtostate 方法不触发。
ProfileEvent
part of 'profile_bloc.dart';
abstract class ProfileEvent extends Equatable {
const ProfileEvent();
@override
List<Object> get props => [];
}
class ProfileScreenSelected extends ProfileEvent {
}
class SaveButtonTapped extends ProfileEvent {
}
class UsernameChanged extends ProfileEvent {
final String value;
UsernameChanged({this.value});
@override
List<Object> get props => [value];
}
class BioChanged extends ProfileEvent {
final String value;
BioChanged({this.value});
@override
List<Object> get props => [value];
}
class CityChanged extends ProfileEvent {
final String value;
CityChanged({this.value});
@override
List<Object> get props => [value];
}
ProfileState
part of 'profile_bloc.dart';
abstract class ProfileState extends Equatable {
const ProfileState();
@override
List<Object> get props => [];
}
class ProfileInitial extends ProfileState {}
class EditingUserInfo extends ProfileState {
final Username username;
final Bio bio;
final PhotoUrl photoUrl;
final City city;
final FormzStatus status;
const EditingUserInfo({
this.username = const Username.pure(),
this.bio = const Bio.pure(),
this.photoUrl = const PhotoUrl.pure(),
this.city = const City.pure(),
this.status = FormzStatus.pure,
});
EditingUserInfo copyWith({Username username, Bio bio, PhotoUrl photoUrl, City city, FormzStatus status}){
return EditingUserInfo(
username: username ?? this.username,
bio: bio ?? this.bio,
photoUrl: photoUrl ?? this.photoUrl,
city: city ?? this.city,
status: status ?? this.status,
);
}
@override
List<Object> get props => [username, bio, photoUrl, city, status];
}
class Loading extends ProfileState {}
class Error extends ProfileState {
final String message;
const Error({this.message});
@override
List<Object> get props => [message];
}
class Success extends ProfileState {
final String message;
const Success({this.message});
@override
List<Object> get props => [message];
}
ProfileBloc
import 'dart:async';
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_native_image/flutter_native_image.dart';
import 'package:formz/formz.dart';
import 'package:meta/meta.dart';
import 'package:muslim_coloc/blocs/authentication/authentication_bloc.dart';
import 'package:muslim_coloc/blocs/profile/cubit/profile_picture_cubit.dart';
import 'package:muslim_coloc/blocs/profile/models/city.dart';
import 'package:muslim_coloc/blocs/profile/models/photoUrl.dart';
import 'package:muslim_coloc/blocs/profile/models/username.dart';
import 'package:muslim_coloc/blocs/profile/models/bio.dart';
import 'package:muslim_coloc/models/user.dart';
import 'package:muslim_coloc/repositories/firebase_user_repository.dart';
import 'package:firebase_storage/firebase_storage.dart';
part 'profile_event.dart';
part 'profile_state.dart';
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
final AuthenticationBloc _authenticationBloc;
final ProfilePictureCubit _profilePictureCubit;
final FirebaseUserRepository _firebaseUserRepository;
StreamSubscription<AuthenticationState> _authSubscription;
ProfileBloc(
{@required authenticationBloc,
@required firebaseUserRepository,
@required profilePictureCubit})
: _authenticationBloc = authenticationBloc,
_profilePictureCubit = profilePictureCubit,
_firebaseUserRepository = firebaseUserRepository,
super(ProfileInitial()) {
_authSubscription = _authenticationBloc.listen((onData) {});
}
@override
Stream<ProfileState> mapEventToState(
ProfileEvent event,
) async* {
if (event is ProfileScreenSelected) {
//when screen is selected, fill in the state fields in order to pre-fill the UI
User user = _authenticationBloc.state.user;
yield EditingUserInfo(
username: Username.dirty(user.username),
bio: (user.bio != null) ? Bio.dirty(user.bio) : Bio.pure(),
photoUrl: (user.photoUrl != null)
? PhotoUrl.dirty(user.photoUrl)
: PhotoUrl.pure(),
city: (user.city != null) ? City.dirty(user.city) : City.pure(),
status: Formz.validate([
Username.dirty(user.username),
(user.bio != null) ? Bio.dirty(user.bio) : Bio.pure(),
(user.photoUrl != null)
? PhotoUrl.dirty(user.photoUrl)
: PhotoUrl.pure(),
(user.city != null) ? City.dirty(user.city) : City.pure(),
]));
} else if (event is UsernameChanged) {
final newUsernameValue = Username.dirty(event.value);
yield (state as EditingUserInfo).copyWith(
username: newUsernameValue,
status: Formz.validate([
newUsernameValue,
(state as EditingUserInfo).bio,
(state as EditingUserInfo).photoUrl,
(state as EditingUserInfo).city
]),
);
} else if (event is BioChanged) {
final newBioValue = Bio.dirty(event.value);
yield (state as EditingUserInfo).copyWith(
bio: newBioValue,
status: Formz.validate([
newBioValue,
(state as EditingUserInfo).username,
(state as EditingUserInfo).photoUrl,
(state as EditingUserInfo).city
]));
} else if (event is CityChanged) {
final newCityValue = City.dirty(event.value);
yield (state as EditingUserInfo).copyWith(
city: newCityValue,
status: Formz.validate([
newCityValue,
(state as EditingUserInfo).username,
(state as EditingUserInfo).photoUrl,
(state as EditingUserInfo).bio
]));
} else if (event is SaveButtonTapped) {
// when save button is tapped, update the authenticated user (bloc) with new info from the state
if (!(state as EditingUserInfo).status.isValidated) return;
User user = _authenticationBloc.state.user;
if (state is EditingUserInfo) {
User updatedUser = User(
id: user.id,
username: (state as EditingUserInfo).username.value,
bio: (state as EditingUserInfo).bio.value,
photoUrl: (state as EditingUserInfo).photoUrl.value,
city: (state as EditingUserInfo).city.value,
favorite: user.favorite,
);
yield Loading();
if (_profilePictureCubit.state.profilePicture != null) {
String photoUrl = await uploadFile(
_profilePictureCubit.state.profilePicture, user.id);
updatedUser = updatedUser.copyWith(photoUrl: photoUrl);
_profilePictureCubit.emptyState();
}
try {
await _firebaseUserRepository.updateUser(updatedUser);
_authenticationBloc.add(AuthenticationUserChanged(
updatedUser)); // Shouldn't go back to home page
/*
When I click the sumbit button and that I successfully sent the data to firebase, the autheticated user changes -as his data are not up to date anymore- and I wait until the updated data are received to launch the Success state
*/
await for (var state in _authenticationBloc) {
if (state.status == AuthenticationStatus.authenticated) {
yield Success(message: 'Le profil a bien été mis à jour');
this.add(ProfileScreenSelected()); // this event does not trigger mapEventToState
}
}
// here, back to editing user info
} on Exception catch (e) {
yield Error(message: e.toString());
// here, back to editing user info
}
print("is this code executed");
//yield ProfileInitial();
}
}
}
Future<String> uploadFile(File _image, String id) async {
Reference profilePhotoReference =
FirebaseStorage.instance.ref('profilePhoto/' + id);
ImageProperties properties =
await FlutterNativeImage.getImageProperties(_image.path);
File compressedFile = await FlutterNativeImage.compressImage(_image.path,
quality: 80,
targetWidth: 300,
targetHeight: (properties.height * 300 / properties.width).round());
profilePhotoReference.putFile(compressedFile);
String returnURL;
//.catchError((){}); //TODO
await profilePhotoReference.getDownloadURL().then((fileURL) {
returnURL = fileURL;
});
return returnURL;
}
}
我也尝试在布局中添加事件,但同样的事情,事件被考虑在内,但 mapEventToState 没有被触发
BlocListener<ProfileBloc, ProfileState>(
listenWhen: (previousState, state) => (state is Error || state is Success) ? true : false,
listener: (context, state) {
Scaffold.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text(
state is Error ? state.message : state is Success ? state.message : ''
),
));
context.read<ProfileBloc>().add(ProfileScreenSelected());
},),
在您的布局文件中,您应该调用这样的 bloc 事件:
profileBloc.add(ProfileScreenSelected());
如果你改变这一行
context.read<ProfileBloc>().add(ProfileScreenSelected());
至此
BlocProvider.of<ProfileBloc>(context).add(ProfileScreenSelected());
应该会有帮助
我刚刚解决了我的问题。对于那些可能面临这种情况的人来说,mapEventToState()
方法被阻止是因为那段代码中的 await for
await for (var state in _authenticationBloc) {
if (state.status == AuthenticationStatus.authenticated) {
yield Success(message: 'Le profil a bien été mis à jour');
this.add(ProfileScreenSelected()); // this event does not trigger mapEventToState
}
}
因此,为了在添加事件后跳出循环,我只是在之后添加了一个 break;
。然后 mapEventToState
方法终于可以触发了。