Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BLoC mapEventToState() not triggered

Tags:

flutter

dart

bloc

Here is my ProfileBloc handling the states of my Profile Screen. In the Bloc, after the user's data are successfully updated, I emit the ProfileScreenSelected() event so that I go back to the pre-filled screen (which corresponds to the EditingUserInfo state), but for some reason this event does not trigger the mapeventtostate() method and I stay in the success state. I don't understand what are the reasons that could lead the mapeventtostate method not to trigger.

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

I also tried by adding the event in the layout but same thing, the event is taken into account, but mapEventToState is not triggered

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

like image 279
user54517 Avatar asked Nov 19 '25 07:11

user54517


1 Answers

I just solved my problem. For those who may face that situation, the mapEventToState() method was blocked because of the await for in that piece of code

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

So in order to get out of the loop once my event is added, I just added a break; right after. And then the mapEventToState method could finally trigger.

like image 127
user54517 Avatar answered Nov 21 '25 21:11

user54517



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!