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 方法终于可以触发了。