BLoC mapEventToState() 未被触发

3
这是我的ProfileBloc,处理我的个人资料屏幕的状态。在Bloc中,当用户数据成功更新后,我会发出ProfileScreenSelected()事件,以便返回到预填充屏幕(对应于EditingUserInfo状态),但由于某种原因,此事件没有触发mapeventtostate()方法,我仍然停留在成功状态。我不明白可能导致mapeventtostate方法不触发的原因。

enter image description here

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()); 
          },),

enter image description here


我触发了ProfileScreenSelected()事件。我在你的代码中没有看到它,我错过了吗? - nvoigt
是的,你错过了。有一个注释在同一行以突出它吗?靠近结尾。 - user54517
2个回答

2

我刚刚解决了我的问题。对于那些可能会遇到类似情况的人来说,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方法终于可以触发。


0
在您的布局文件中,您应该像这样调用块事件:
profileBloc.add(ProfileScreenSelected());

如果你改变了这一行

context.read<ProfileBloc>().add(ProfileScreenSelected()); 

转换成这个

BlocProvider.of<ProfileBloc>(context).add(ProfileScreenSelected()); 

它应该有所帮助


是的,我也尝试在我的布局中使用Bloc监听器来实现这个,但那也是同样的事情。 - user54517
@user54517 分享你的布局代码。你不必在监听器中完成这个操作。 - Vadim Popov
1
我编辑了这篇文章。你的意思是说我不需要在监听器中执行这个操作吗?我想在成功状态后立即执行该操作。因此,我监听了该状态,并且当我处于该状态时,我可以添加事件以切换到下一个状态,理论上来说。 - user54517
好的,让我考虑一下。 - Vadim Popov
1
初始化?嗯,在我的main.dart文件中。你认为这可能有帮助吗?BlocProvider( create: (_) => ProfileBloc( authenticationBloc: BlocProvider.of<AuthenticationBloc>(_), firebaseUserRepository: userRepository, profilePictureCubit: BlocProvider.of<ProfilePictureCubit>(_)), ),我的意思是,除了第二次启动ProfileScreenSelected时出现问题外,所有状态都能正常工作。 - user54517
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接