【问题标题】:BLoC mapEventToState() not triggeredBLoC mapEventToState() 未触发
【发布时间】:2021-05-20 05:42:36
【问题描述】:

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

个人资料事件

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];  
}

个人资料状态

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

【问题讨论】:

  • “我发出 ProfileScreenSelected() 事件”我在代码中的任何地方都看不到它。我错过了吗?
  • 是的,你错过了。在同一行有注释来突出显示它?接近尾声

标签: flutter dart bloc


【解决方案1】:

在你的布局文件中,你应该像这样调用一个 bloc 事件:

profileBloc.add(ProfileScreenSelected());

如果你改变这一行

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

到这里

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

应该有帮助

【讨论】:

  • 是的,我也尝试在 bloc 监听器的布局中这样做,但这是同一件事
  • @user54517 分享您的布局代码。您不必在 lisner 中执行此操作
  • 我编辑了这篇文章。你是什​​么意思我不必在听众中这样做?我想在我的成功状态之后立即这样做。所以我听了那个状态,当我在这个状态时,我可以添加事件切换到下一个状态,理论上
  • 好吧,让我想想
  • 初始化?嗯,在我的 main.dart 你认为这有帮助吗? BlocProvider( create: (_) =&gt; ProfileBloc( authenticationBloc: BlocProvider.of&lt;AuthenticationBloc&gt;(_), firebaseUserRepository: userRepository, profilePictureCubit: BlocProvider.of&lt;ProfilePictureCubit&gt;(_)), ), 我的意思是所有状态都运行良好,除了我第二次启动 ProfileScreenSelected 时。
【解决方案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方法终于可以触发了。

【讨论】:

    猜你喜欢
    • 2020-07-05
    • 1970-01-01
    • 1970-01-01
    • 2020-08-25
    • 2021-12-05
    • 2022-07-10
    • 2022-07-26
    • 2021-04-21
    • 1970-01-01
    相关资源
    最近更新 更多